7

backgroundWorker 的全部意义在于在耗时的任务之后更新 UI。该组件的工作方式与我的 WPF 应用程序中宣传的一样。

然而,在我的测试中,回调没有在调用线程上调用。

[Test]
public void TestCallbackIsInvokedOnClientThread()
{

     var clientId = Thread.CurrentThread.ManagedThreadId;
     int callbackThreadId = -1;
     var manualEvent = new ManualResetEventSlim(false);

     var someUIControl = new TextBox();
     var bw = new BackgroundWorker();

     bw.DoWork += (s,e) => e.Result = 5 ; // worker thread

     bw.RunWorkerCompleted += (s, e) =>
                                  {
                                      try
                                      {
                                          callbackThreadId = Thread.CurrentThread.ManagedThreadId;
                                          //someUIControl.Text = callbackThreadId.ToString();
                                          manualEvent.Set();
                                      }
                                      catch (System.Exception ex)
                                      {
                                          Console.Out.WriteLine(ex.ToString());
                                      }
                                  };
     bw.RunWorkerAsync();

     if (!manualEvent.Wait(5000))
         Assert.Fail("no callback");
     Assert.AreEqual(clientId, callbackThreadId);
 }

结果消息:Assert.AreEqual 失败。预期:<15>。实际:<10>。未在客户端线程上调用回调

我错过了什么?

在单元测试中,我看到了类似的行为

------ Run test started ------
MainThread Id =21
Worker Thread Id =9
Callback Thread Id =9

在 Wpf 应用程序中,这将是

MainThread Id =1
Worker Thread Id =14
Callback Thread Id =1

更新: 根据贾斯汀的回答,进行了以下更改,现在测试通过了

  • 在创建 BackgroundWorker 之前 SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext(control.Dispatcher));
  • 不是使用事件在线程之间发出信号,而是模拟消息泵

.

for (int i = 0; i < 3; i++)
{
    control.Dispatcher.Invoke(DispatcherPriority.Background,
                                          new Action(delegate { }));
    Thread.Sleep(50);
}
4

4 回答 4

10

由于您正在运行的不同上下文,行为是不同的。

当您调用 bw.RunWorkerAsync() 时,会捕获 SynchronizationContext。这用于分派 RunWorkerCompleted 调用。

在 WPF 下,它将使用 DispatcherSynchronizationContext 将完成的调用编组回 UI 线程。在测试中,这种编组是不必要的,因此它保留在后台工作线程上。

于 2012-11-23T11:20:26.893 回答
3

我相信调用线程必须支持消息泵送(意味着是 STA 单元并具有关联的调度程序),以便后台工作人员可以发布回调。如果没有,后台工作人员别无选择,只能在自己的线程中执行回调。如果您想测试它,请参阅此链接

于 2012-11-23T11:17:25.850 回答
0

I ran into a problem in my code where the user closing a window caused a save, that in turn used a BackgroundWorker to update the home window and it did not run the RunWorkerCompleted because the thread that started the BackgroundWorker had terminated when the window closed.

I had to change the closing window's save run in the home window's context so that after the BackgroundWorker completed, it had a thread to return to.

于 2013-01-23T22:28:05.887 回答
0

在我的情况下,我使用的是 Windows 窗体和控件没有Dispatcher属性(请参阅no definition for dispatcher的答案)。

如果我们使用Dispatcher.CurrentDispatcher而不是控件中的那个, Gishu 的解决方案同样有效。

在测试初始化​​时:

// I am using a field Dispatcher _dispatcher
_dispatcher = Dispatcher.CurrentDispatcher; 

然后在等待后台任务完成时:

_dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { }));
Thread.Sleep(50);
于 2020-03-06T10:59:23.507 回答