Task.Factory.StartNew 与 TaskScheduler.Default 冻结 UI。网络4.0

Task.Factory.StartNew with TaskScheduler.Default freezes UI. Net 4.0

我读过一个 Stephen Cleary's article 有必要明确地设置从 TaskScheduler.CurrentTaskScheduler.Default

当我的程序启动时,数据加载就开始了。大约需要 5 秒。如果我在加载数据时单击按钮 添加 ,那么新的 window 将不会打开,直到方法 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。但是我的示例无法正常工作(加载数据时未打开新 window),因此显示错误行为 I've made a test application and it can be downloaded here.。因为我听到很多关于使用 TaslScheduler.Default.

的评论

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

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

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

可能不是你的问题,因为我刚刚在调用

之外看到了你的 thread.sleep

你的问题之一在于这段代码:

   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());

另见 Task continuation on UI thread

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

    public AddPersonVM()
    {
        LoadData();

        AddPersonCommand = new RelayCommand<object>(AddPerson);
        CancelPersonCommand = new RelayCommand<object>(CancelPerson);
    }

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

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

在 MainWindow 中,您有以下代码:

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 未直接显示。