c# - BackgroundWorker RunWorkerCompleted 未在 UI 线程上执行

标签 c# wpf multithreading mvvm backgroundworker

我在 BackgroundWorker 周围写了一个小包装:

public class Worker
{
  public void Execute<T>(Func<T> work, Action<Exception, T> workCompleted)
  {
    var worker = new BackgroundWorker();
    worker.DoWork += (o, ea) => { ea.Result = work(); };
    worker.RunWorkerCompleted += (o, ea) => { workCompleted(ea.Error, (T)ea.Result); };
    worker.RunWorkerAsync();
  }
}

我在 View 模型(在 XAML 中创建)中使用它,如下所示:

public class MainWindowViewmodel : INotifyPropertyChanged
{
  private ObservableCollection<Job> _jobs = new ObservableCollection<Job>();
  public ICollectionView JobsView { get; } = CollectionViewSource.GetDefaultView(_jobs);
  public RelayCommand RefreshJobsCommand => new RelayCommand(x => UpdateJobs()); /*edit1*/

  private void UpdateJobs() /*executed via command binding*/
  {
    new Worker().Execute(() => _webServiceManager.JobService.GetJobsByDate(settings.Limit, DateTime.Now.AddDays(-365)), (ex, jobArray) =>
      {
        jobArray.toList().ForEach(j=>_jobs.Add(j));
      });
  }
}

workCompleted 中添加作业时到 ObservableCollection _jobs ,我得到以下异常:This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
很明显 RunWorkerCompleted回调不在 UI 线程上运行。但据我所知,它应该在 RunWorkerAsync 所在的一个线程上运行。被称为?这应该是 UI 线程,因为它是从命令绑定(bind)(也是 UI 线程)的 View 模型(从 XAML 创建)内部调用的,对吗?

我在这里想念什么?

编辑2:
我可以将问题缩小到作业的初始加载。这是由 MainWindow 的 ctor 完成的。并将导致所描述的问题。之后通过命令绑定(bind)使用 UI 按钮进行的​​任何其他更新都可以正常工作。

所以问题是,在初始加载时,MainWindow导演,ViewModelWorker.Execute()在 UI 线程上执行,但 workCompleted不是,尽管 SynchronizationContext在调用 RunWorkerAsync 时应设置为 UI 线程.

每当通过命令绑定(bind)触发下一次加载时,workCompleted也设置为 SynchronizationContext UI 线程。

知道为什么这首先不起作用吗?
public partial class MainWindow : Window
{
   private MainWindowViewmodel _viewmodel;

   public MainWindow(Connection connection)
   {
      InitializeComponent();
      Debug.WriteLine("mainwindow-ctor: " + Thread.CurrentThread.ManagedThreadId.ToString());
      _viewmodel = (MainWindowViewmodel)DataContext;
      _viewmodel.UpdateJobs();
   }
}

最佳答案

RunWokerCompleted 所在的线程事件处理由 SynchronizationContext.Current 决定当时的属性(property)RunWorkerAsync方法被调用。

因此,如果您的 Execute方法实际上是在 WPF 应用程序的 UI 线程上调用的,其中 SynchronizationContext.Current属性返回 DispatcherSynchronizationContext , workCompleted也将在此线程上调用。您可以通过调用 Execute() 轻松地自行确认。在 MainWindow 的构造函数中:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        new Worker().Execute(() => { Thread.Sleep(5000); return 0; }, (ex, jobArray) =>
        {
            MessageBox.Show(Thread.CurrentThread.ManagedThreadId.ToString());
        });
    }
}

顺便说一句,Task Parallel Library (TPL)是自 .NET Framework 4 以来编写多线程和并行代码的首选方式。任务异步编程模型 (TAP) 和 asyncawait .NET 4.5 中引入的关键字使其更易于使用。

关于c# - BackgroundWorker RunWorkerCompleted 未在 UI 线程上执行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57807973/

相关文章:

c# - 如何触发 WPF 上下文菜单的关闭动画?

c# - 涉及异步调用时如何跟踪工作单元?

java - Java 中没有同步的线程安全单例?

c# - RadGrid上的筛选不起作用

c# - 在 C# 中捕获和编辑 TCP/UDP 数据包

c# - 嵌入式文件系统?

python - 继承threading.Thread类不起作用

c# - 消息队列 - 同时处理多个响应 - C#.NET

c# - 交替背景颜色网格行不起作用

c# - 按住鼠标的 WPF 日历控件