【问题标题】:Task.Factory.StartNew with TaskScheduler.Default freezes UI. Net 4.0Task.Factory.StartNew 与 TaskScheduler.Default 冻结 UI。净4.0
【发布时间】:2016-09-21 17:22:49
【问题描述】:

我读过一个Stephen Cleary's article,它必须从TaskScheduler.Current 显式设置为TaskScheduler.Default

当我的程序启动时,就会开始加载数据。大约需要 5 秒钟。如果我在加载数据的时候点击Add按钮,那么直到Task方法ReadData()的操作没有完成,新窗口才会打开。

视图模型:

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。但是我的示例无法正常工作(加载数据时未打开新窗口),因此显示错误行为I've made a test application and it can be downloaded here.。因为我听说很多 cmets 使用TaslScheduler.Default

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

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

【问题讨论】:

  • 如果您调用Dispatcher.(Begin)Invoke(),您将在 UI 线程上执行 lambda 表达式。删除调用,仅在需要更新 UI 控件/元素时调用。
  • @VisualVincent 如果我删除 Dispatcher,它仍然会冻结 UI。
  • @VisualVincent 我想使用 Thread.Sleep(5000);导致它冻结任务线程。它正在模仿我的程序的行为。
  • 我目前正在使用手机,但我可以在大约 40-60 分钟内进行测试。
  • 是的,通过使用 NuGet 包。 blogs.msdn.microsoft.com/bclteam/2012/10/22/… 但仅限于 VS 2012 及更高版本

标签: c# .net wpf .net-4.0 task-parallel-library


【解决方案1】:

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

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

【讨论】:

  • 感谢您的尝试,但它仍然会冻结 UI。原因是什么真的很有趣。我已经按照你的建议做了。也许您还有其他建议?我愿意接受任何建议:)。
  • @StepUp 指导您帮助创建响应式应用程序需要做大量工作。我会尽力给你一些建议,请参阅更新的答案。您必须考虑您的同步方法以及调用它们的最佳位置。
  • 感谢您的帮助,但为什么 That code has to complete before ShowDialog() is called. 我们使用多线程。为什么LoadData()的线程要等待UI线程?
  • AddPersonVM 中的 LoadData 方法不是异步的。只有 MainWindowVM 中的 ReadDataAsunchronous() 方法是。当我添加一个 Thread.Sleep(60*5*1000);在 ReadDataAsunchronous() 中,我单击“添加”按钮,由于 AddPersonVM 中的 LoadData 方法,它仍然需要大约 5 秒钟才能显示出来。所以我们可以得出结论,ReadDataAsunchronous() 不是唯一的问题(因为我不必等待 5 分钟就可以出现 Add Person 对话框。
  • 我尝试在 MainWindow 的 Loaded 事件中加载数据。而且没有任何改进。数据正在加载,我看不到新打开的窗口。
【解决方案2】:

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

可能不是你的问题,因为我刚刚在调用之外看到了你的 thread.sleep

【讨论】:

  • 如果我删除 Dispatcher,它仍然会冻结 UI。
猜你喜欢
  • 2023-03-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-25
  • 2015-01-16
  • 2019-09-16
  • 1970-01-01
  • 2015-04-20
相关资源
最近更新 更多