为什么BackgroundWorker的不叫RunWorkerCompleted在这个单元测试正确的线程上?在这个、线程、不叫、单元测试

2023-09-04 13:34:29 作者:7.徒有野心勃勃

BackgroundWorker的整点是更新一个耗时的任务后,用户界面​​。该组件的工作在我的WPF应用程序像宣传的那样

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

  [测试]
公共无效TestCallbackIsInvokedOnClientThread()
{

     VAR客户端ID = Thread.CurrentThread.ManagedThreadId;
     INT callbackThreadId = -1;
     VAR manualEvent =新ManualResetEventSlim(假);

     VAR someUIControl =新文本框();
     VAR体重=新的BackgroundWorker();

     bw.DoWork + =(S,E)=> e.Result = 5; //工作者线程

     bw.RunWorkerCompleted + =(S,E)=>
                                  {
                                      尝试
                                      {
                                          callbackThreadId = Thread.CurrentThread.ManagedThreadId;
                                          //someUIControl.Text = callbackThreadId.ToString();
                                          manualEvent.Set();
                                      }
                                      赶上(System.Exception的前)
                                      {
                                          Console.Out.WriteLine(ex.ToString());
                                      }
                                  };
     bw.RunWorkerAsync();

     如果(!manualEvent.Wait(5000))
         Assert.Fail(不回调);
     Assert.AreEqual(客户端ID,callbackThreadId);
 }
 

结果消息:Assert.AreEqual失败。预计:< 15>。实际:小于10>。回调无法在客户端线程中调用

我在想什么?

在单元测试中我看到这样

行为

  ------运行试验开始------
MainThread n = 21
工作者线程ID = 9
回调线程ID = 9
 

在WPF应用程序,这将是

  MainThread ID = 1
工作者线程ID = 14
回调线程ID = 1
 
深入分析java线程池的实现原理

更新: 与贾斯汀的回答,进行了如下修改,现在测试通过了

在创建BackgroundWorker的 SynchronizationContext.SetSynchronizationContext(新DispatcherSynchronizationContext(control.Dispatcher)); 而不是使用一个事件的线程之间的信令,模拟消息泵

 的for(int i = 0;我3;;我++)
{
    control.Dispatcher.Invoke(DispatcherPriority.Background,
                                          新的行动(委托{}));
    Thread.sleep代码(50);
}
 

解决方案

该行为是不同的会费,你是下运行的不同情况。

当你调用bw.RunWorkerAsync(),该的SynchronizationContext被捕获。这是用来分派出RunWorkerCompleted呼叫

在WPF将使用DispatcherSynchronizationContext将马歇尔完成回调到UI线程。在测试中,这种编组是不​​必要的,因此仍然是后台工作线程。

The whole point of the backgroundWorker is to update the UI after a time-consuming task. The component works as advertised in my WPF app.

However in my test, the callback is not invoked on the calling thread.

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

Result Message: Assert.AreEqual failed. Expected:<15>. Actual:<10>. callback not invoked on client Thread

What am I missing ?

In the Unit Test I see behavior like

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

In the Wpf App, this would be

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

Update: With Justin's answer, made the following changes and now the test passes

Before creating the BackgroundWorker SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext(control.Dispatcher)); Instead of using a event for signalling between the threads, simulate a message pump

.

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

解决方案

The behavior is different dues to the different contexts that you are running under.

When you call bw.RunWorkerAsync(), the SynchronizationContext is captured. This is used to dispatch out the RunWorkerCompleted call.

Under WPF it will use DispatcherSynchronizationContext which will marshall the completed call back to the UI thread. Under the test, this marshalling is unnecessary so it remains on the background worker thread.