【问题标题】:Why does the BackgroundWorker not call the RunWorkerCompleted on the right thread in this unit test?为什么BackgroundWorker在这个单元测试中没有在正确的线程上调用RunWorkerCompleted?
【发布时间】:2012-11-23 10:45:00
【问题描述】:

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 失败。预期:。实际:。未在客户端线程上调用回调

我错过了什么?

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

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

【问题讨论】:

  • 你检查过backgroundworker线程的id吗?也许它也没有被调用。
  • @Tudor - 更新帖子,提供更多信息。

标签: c# .net multithreading


【解决方案1】:

由于您运行的上下文不同,行为会有所不同。

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

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

【讨论】:

  • 这似乎是缺失的部分 - SynchronizationContext.Current 在单元测试中为空。
【解决方案2】:

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

【讨论】:

  • 是的,它怎么可能劫持正在做其他事情的线程?
【解决方案3】:

我在我的代码中遇到了一个问题,用户关闭窗口导致保存,然后使用 BackgroundWorker 更新主窗口,但它没有运行 RunWorkerCompleted,因为启动 BackgroundWorker 的线程在窗口关闭。

我必须在主窗口的上下文中更改关闭窗口的保存运行,以便在 BackgroundWorker 完成后,它有一个线程可以返回。

【讨论】:

    【解决方案4】:

    在我的情况下,我使用的是 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);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-01-08
      • 1970-01-01
      • 2011-11-29
      • 1970-01-01
      • 2019-06-28
      • 2016-06-27
      • 1970-01-01
      • 2020-11-21
      相关资源
      最近更新 更多