Task.Factory.StartNew with TaskScheduler.默认冻结UI.Net 4.0

本文关键字:UI Net 冻结 默认 Factory StartNew with TaskScheduler Task | 更新日期: 2023-09-27 18:08:14

我读过Stephen Cleary的一篇文章,有必要显式地设置从TaskScheduler.CurrentTaskScheduler.Default

当我的程序启动时,然后开始加载数据。大约需要5秒。如果我在加载数据时点击Add按钮,则新窗口不会打开,直到ReadData()方法的Task操作未完成。

ViewModel:

public class MainWindowVM:ViewModelBase
{
      public MainWindowVM()
      { 
        AddPersonCommand = new RelayCommand<object>(AddPerson);                     
        ReadData();
    }
    private void ReadData()
    {
       PersonData = new ObservableCollection<PersonDepBureau>();
       IList<PersonDepBureau> persons;           
       Task.Factory.StartNew(() =>
        {
            using (PersonDBEntities db = new PersonDBEntities())
            {
                persons = (from pers in db.Person
                        join bu in db.Bureau on pers.Fk_IdBureau equals bu.IdBureau
                        join dep in db.Departament on pers.Fk_IdDep equals dep.IdDep
                        select new PersonDepBureau
                        {
                            IdPerson = pers.IdPerson,
                            PersonName = pers.Name,
                            Surname = pers.Surname,
                         ).ToList();
                Application.Current.Dispatcher.BeginInvoke(new Action(() =>
                {
                    foreach (var person in persons)
                    {
                        PersonData.Add(person);
                    }
                }));  
            }
        }, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);           
    }
    //Opening new Window AddPersonWindow.xaml
    private void AddPerson(object obj)
    {
        AddPersonWindow addPersonWindow = new AddPersonWindow();
        addPersonWindow.DataContext new AddPersonVM();            
        addPersonWindow.ShowDialog();
    }
}

视图:

<extToolkit:BusyIndicator IsBusy="{Binding IsBusy}" Foreground="Black">
   <DataGrid ItemsSource="{Binding PersonData}" SelectedItem="{Binding SelectedPerson}"  
      IsSynchronizedWithCurrentItem="True">
     <DataGrid.Columns>
       <DataGridTextColumn Header="Name"  Binding="{Binding PersonName}"/>
       <DataGridTextColumn Header="Surname" Binding="{Binding Surname}" />           
     </DataGrid.Columns>
    </DataGrid>
</extToolkit:BusyIndicator>
<Grid Grid.Row="1">    
  <Button Command="{Binding AddPersonCommand}" Margin="5" Grid.Column="1">Add</Button>
</Grid>

不能使用async/await语法糖,因为我的应用程序可以在Windows XP中使用(我不能要求用户安装.NET 4.5)。谢谢。

这是非常奇怪的行为。我读过的所有信息都是像我一样使用Task。但是我的例子不能正常工作(加载数据时没有打开新窗口),所以为了显示错误行为,我做了一个测试应用程序,它可以在这里下载。因为我听到了很多使用TaslScheduler.Default的评论。

请不要关闭我的线程,因为了解我的UI无响应的原因对我来说非常重要。

Stephen Cleary的文章很棒。感谢Peter Bons的耐心。构造Task.Factory.StartNew(() =>}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);是完美的工作,冻结UI的原因是在UI线程上执行的另一个操作

Task.Factory.StartNew with TaskScheduler.默认冻结UI.Net 4.0

您的问题之一在于以下代码:

   Task.Factory.StartNew(() =>
        {
            using (PersonDBEntities db = new PersonDBEntities())
            {
                try
                {
                    persons = (from pers in db.Person
                               join bu in db.Bureau on pers.Fk_IdBureau equals bu.IdBureau
                               join dep in db.Departament on pers.Fk_IdDep equals dep.IdDep
                               select new PersonDepBureau
                               {
                                   IdPerson = pers.IdPerson,
                                   PersonName = pers.Name,
                                   Surname = pers.Surname,
                                   CurrentBureau = bu,
                                   CurrentDepartament = dep
                               }).ToList();
                    Application.Current.Dispatcher.BeginInvoke(new Action(() =>
                    {
                        foreach (var person in persons)
                        {
                            PersonData.Add(person);
                        }
                    }));  
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }     
            }
            IsBusy = false;
        }, CancellationToken.None, TaskCreationOptions.AttachedToParent, TaskScheduler.Default);

您正在尝试从另一个线程更新UI。这就是为什么要使用调度器。您应该将数据加载从实际(UI)处理逻辑中分离出来。因为你不能使用async/await,你需要使用ContinueWith

类似(伪代码):

var loadDataTask = Task.Factory.StartNew(() =>
        {
            var persons = new List<PersonDepBureau>();
            using (PersonDBEntities db = new PersonDBEntities())
            {
                    persons = (from pers in db.Person
                               join bu in db.Bureau on pers.Fk_IdBureau equals bu.IdBureau
                               join dep in db.Departament on pers.Fk_IdDep equals dep.IdDep
                               select new PersonDepBureau
                               {
                                   IdPerson = pers.IdPerson,
                                   PersonName = pers.Name,
                                   Surname = pers.Surname,
                                   CurrentBureau = bu,
                                   CurrentDepartament = dep
                               }).ToList();
            }
            return persons;
        }, CancellationToken.None, TaskCreationOptions.AttachedToParent, TaskScheduler.Default);
loadDataTask.ContinueWith((t) =>
{
    // you can check t.Exception for errors
    // Do UI logic here. (On UI thread)  
    foreach (var person in t.Result)
    {
          PersonData.Add(person);
    }
}, TaskScheduler.FromCurrentSynchronizationContext());

参见UI线程上的任务延续

然后另一个问题是,当您按下添加按钮时,创建了AddPersonVM的实例。在构造函数中使用以下代码:

    public AddPersonVM()
    {
        LoadData();
        AddPersonCommand = new RelayCommand<object>(AddPerson);
        CancelPersonCommand = new RelayCommand<object>(CancelPerson);
    }

当我计算LoadData()调用的持续时间时,它需要1.5到4s完成。该代码必须在调用ShowDialog()之前完成。

不应该在构造函数中加载数据。在你的例子中,不是在对话框显示之前。你既可以异步加载数据,也可以打开对话框并使用Window。加载事件以开始获取数据(同步或异步)。因为此时UI已经显示,所以您可以在从数据库获取数据时使用忙指示符。

在主窗口中你有这样的代码:

private void AddPerson(object obj)
    {
        AddPersonWindow addPersonWindow = new AddPersonWindow();
        AddPersonVM addPersonVM = new AddPersonVM(); // This synchronous code takes about 2 to 5 seconds to complete
        addPersonWindow.DataContext = addPersonVM;            
        addPersonVM.OnRequestClose += (sender, args) => { addPersonWindow.Close(); };
        addPersonVM.OnPersonSave += addPersonVM_OnPersonSave;
        addPersonWindow.ShowDialog();
    }

由于AddPersonVM的构造函数从数据库加载数据需要大约2到5秒才能完成,因此没有直接显示addPersonWindow

您正在Dispatcher上做您的工作.....

可能不是你的问题,因为我刚刚看到了你的帖子。