天道酬勤,学无止境

Why does the BackgroundWorker not call the RunWorkerCompleted on the right thread in this unit test?

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.

I belive that the calling thread must support messagepumping (mean, being STA apartment and having an associated Dispatcher) so the background worker can post the callback. If it does not, the background worker has no option but execute the callback in its own thread. If you want to test it, see this link.

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.

In my case I am using Windows Forms and controls don't have a Dispatcher property (see the answer in no definition for dispatcher).

Gishu's solution works as well if we use Dispatcher.CurrentDispatcher instead of the one in the control.

On test initialisation:

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

And then when waiting for the background task to be completed:

_dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { }));
Thread.Sleep(50);

受限制的 HTML

  • 允许的HTML标签:<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • 自动断行和分段。
  • 网页和电子邮件地址自动转换为链接。

相关推荐
  • C#:我需要处理在运行时创建的 BackgroundWorker 吗?(C#: Do I need to dispose a BackgroundWorker created at runtime?)
    问题 我通常在表单上有这样的代码: private void PerformLongRunningOperation() { BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += delegate { // perform long running operation here }; worker.RunWorkerAsync(); } 这意味着我不会处理BackgroundWorker ,而如果我是由表单设计者添加的,那么我认为它会被处理。 这会导致任何问题吗? 声明一个模块级别的_saveWorker ,然后从表单的 dispose 方法对其调用Dispose是否更正确? 回答1 是的,您应该处理后台工作人员。 您可能会发现使用ThreadPool.QueueUserWorkItem(...)更容易,之后不需要任何清理。 关于为什么应该始终调用 Dispose() 的其他详细信息: 尽管如果您查看 BackgroundWorker 类,它实际上并没有在它的 dispose 方法中进行任何线程清理,但由于该类对垃圾收集器的影响,调用 Dispose 仍然很重要。 带有终结器的类不会立即被 GC。 它们被保留并添加到终结器队列中。 然后终结器线程运行,(遵循标准模式调用 dispose)。
  • 为什么内部异常到达ThreadException处理程序而不是实际抛出的异常?(Why does the inner exception reach the ThreadException handler and not the actual thrown exception?)
    问题 在引发异常并将其捕获到Application.ThreadException事件处理程序中时,我看到一些奇怪的行为。 基本上,以下示例中发生的事情是BackgroundWorker的DoWork事件处理程序中引发了异常。 RunWorkerCompleted事件处理程序将引发一个新异常,而原始事件将其作为内部异常。 为什么内部异常会显示在ThreadException事件处理程序中,而不会抛出严重异常? 如果我未在RunWorkerCompleted事件处理程序中提供内部异常,则将显示正确的异常。 using System; using System.Windows.Forms; using System.ComponentModel; namespace WierdExceptionApp { class WierdExceptionForm : Form { BackgroundWorker worker = new BackgroundWorker(); public WierdExceptionForm() { worker.DoWork += new DoWorkEventHandler(worker_DoWork); worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker
  • BackgroundWorker中未处理的异常(Unhandled exceptions in BackgroundWorker)
    问题 我的WinForms应用程序使用许多BackgroundWorker对象从数据库检索信息。 我使用BackgroundWorker是因为它允许UI在长时间运行的数据库查询期间保持畅通无阻,并且为我简化了线程模型。 我在其中一些后台线程中偶尔遇到DatabaseException,并且在调试时目睹了在工作线程中至少有这些异常之一。 我非常有信心这些例外是超时,我认为这是不时会发生的合理情况。 我的问题是有关这些后台工作线程之一发生未处理的异常时会发生什么情况。 我认为我无法在另一个线程中捕获异常,但是我可以期望我的WorkerCompleted方法被执行吗? 是否可以查询BackgroundWorker的任何属性或方法来处理异常? 回答1 如果该操作引发了代码无法处理的异常,则BackgroundWorker捕获该异常并将其传递到RunWorkerCompleted事件处理程序,在该事件处理程序中将其RunWorkerCompleted为System.ComponentModel.RunWorkerCompletedEventArgs的Error属性。 如果您在Visual Studio调试器下运行,则调试器将在DoWork事件处理程序中引发未处理异常的点处中断。 http://msdn.microsoft.com/zh-CN/library/system
  • 为什么BackgroundWorker总是很忙?(Why BackgroundWorker always is busy?)
    问题 我在WPF应用程序的后台工作人员中发现了一些奇怪的事情。 我现在要完成的工作是等待BW完成启动另一个线程。 检查以下代码: if (bw.IsBusy) { bw.CancelAsync(); System.Threading.ThreadStart WaitThread = new System.Threading.ThreadStart(delegate() { while (bw.IsBusy) { System.Threading.Thread.Sleep(100); } bw.RunWorkerAsync(); }); System.Windows.Application.Current.Dispatcher.Invoke( System.Windows.Threading.DispatcherPriority.Normal, WaitThread); // if I remove this line, bw fires RunWorkerAsyncEvent } else { bw.RunWorkerAsync(); } 请注意,我添加了Dispatcher.Invoke以等待bw不忙,但是如果我调用它,则永远忙,并且从不触发RunWorkerAsyncCompleted事件。 不过,如果我删除该行,则可以触发该事件,这真的很奇怪。 我要如何等到bw完成? 回答1
  • 从 WPF 中的后台工作人员更新进度条(Update progressbar from a backgroundworker in WPF)
    问题 我正在尝试使用BackgroundWorker使进度条前进。 最终目标是显示后台搜索的进度,但我首先想通过做一个简单的模拟来了解进度条。 这是代码: public MainWindow() { InitializeComponent(); worker = new BackgroundWorker(); // variable declared in the class worker.WorkerReportsProgress = true; worker.DoWork += new DoWorkEventHandler(worker_DoWork); worker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged); worker.RunWorkerCompleted += worker_RunWorkerCompleted; } private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { this.Title += " DONE"; } private void worker_DoWork(object sender
  • 同步调用BackgroundWorker(Calling BackgroundWorker synchronously)
    问题 我想同步调用后台工作者。 我希望在后台工作程序完成执行后结束代码的执行。 我的 BackgroundWorker 代码在这里: { BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += DoWork; worker.RunWorkerCompleted += RunWorkerCompleted; ... worker.RunWorkerAsync(); //wait for execution to end } 这样做的一种方法是再次检查状态 n 直到其执行完成,但还有其他好的方法吗? 回答1 如果您不希望代码异步执行,请不要将其放入BackgroundWorker ... { DoWork(); } 但是,如果您绝对需要将代码放在BackgroundWorker有一些模糊的原因,则可以使用以下内容: ManualResetEvent mre = new ManualResetEvent(false); BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += DoWork; worker.RunWorkerCompleted += (s, e) => { RunWorkerCompleted(s, e); mre
  • BackgroundWorkers永不停止忙碌(BackgroundWorkers never stop being busy)
    问题 for (do it a bunch of times) { while (backgroundWorker1.IsBusy && backgroundWorker2.IsBusy && backgroundWorker3.IsBusy && backgroundWorker4.IsBusy && backgroundWorker5.IsBusy) { System.Threading.Thread.Sleep(0001); } if (!backgroundWorker1.IsBusy) { backgroundWorker1.RunWorkerAsync(); } else if (!backgroundWorker2.IsBusy) { backgroundWorker2.RunWorkerAsync(); } else if (!backgroundWorker3.IsBusy) { backgroundWorker3.RunWorkerAsync(); } else if (!backgroundWorker4.IsBusy) { backgroundWorker4.RunWorkerAsync(); } else if (!backgroundWorker5.IsBusy) { backgroundWorker5.RunWorkerAsync(); } }
  • 启动画面等待,直到线程完成(Splash Screen waiting until thread finishes)
    问题 我仍然对启动屏幕有问题。 我不想使用属性SC.TopMost=true 。 现在我的应用场景如下: 在progeram.cs中: [STAThread] static void Main() { new SplashScreen(_tempAL);// where _tempAL is an arrayList Application.Run(new Form1(_tempAL)); } 在SplashScreen类中: public SplashScreen(ArrayList _Data) { DisplaySplash() } private void DisplaySplash() { this.Show(); this.TopMost = true; this.CenterToScreen(); this.SetTopLevel(true); _allServerNarrators = new string[10]; for (int i = 0; i < _allServerNarrators.Length; i++) _allServerNarrators[i] = null; GetFromServer(); this.Hide(); _serverData = new ArrayList(); _thisData.Add(
  • BackgroundWorker无法在VSTO中工作(BackgroundWorker Not working in VSTO)
    问题 我有一名背景工作者。 在调用工作程序之前,请先禁用按钮并使gif可见。 然后,我调用runworkerasync方法,该方法可以正常运行直到完成。 在“ RunWorkerCompleted()”上,我收到一个跨线程错误。 知道为什么吗? private void buttonRun_Click(object sender, EventArgs e) { if (comboBoxFiscalYear.SelectedIndex != -1 && !string.IsNullOrEmpty(textBoxFolderLoc.Text)) { try { u = new UpdateDispositionReports( Convert.ToInt32(comboBoxFiscalYear.SelectedItem.ToString()) , textBoxFolderLoc.Text , Properties.Settings.Default.TemplatePath , Properties.Settings.Default.ConnStr); this.buttonRun.Enabled = false; this.pictureBox1.Visible = true; BackgroundWorker bw = new BackgroundWorker(); bw
  • 当RunWorkerAsync被调用一次时,BackgroundWorker的DoWork被调用两次?(DoWork of BackgroundWorker is called twice when RunWorkerAsync is called once?)
    问题 我已经在一个可以工作的类中创建了一个backgroundworker,但是如果我调用并等到结束运行,则第二次调用它,它将执行相同的过程两次 我认为bw.DoWork有什么问题+ = private void button1_Click(object sender, EventArgs e) { nptest.test.start("null", "null"); } namespace nptest { class test { public static void start(string str, string strb) { if (bw.IsBusy != true) { bw.WorkerSupportsCancellation = true; bw.DoWork += (obj, e) => bw_DoWork(str, strb); bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted); bw.RunWorkerAsync(); } } private static BackgroundWorker bw = new BackgroundWorker(); private static void bw_DoWork(string str, string
  • BackgroundWorker中未处理的异常(Unhandled exceptions in BackgroundWorker)
    问题 我有一个小型WinForms应用程序,该应用程序使用BackgroundWorker对象执行长时间运行的操作。 后台操作偶尔会引发异常,通常是当有人打开了正在重新创建的文件时。 不管代码是否从IDE运行,.NET都会弹出一个错误对话框,通知用户发生了未处理的异常。 使用Release配置编译代码也不会对此进行更改。 根据MSDN: 如果该操作引发了代码无法处理的异常,则BackgroundWorker将捕获该异常并将其传递到RunWorkerCompleted事件处理程序,在该事件处理程序中将其公开为System.ComponentModel .. ::。RunWorkerCompletedEventArgs的Error属性。 如果您在Visual Studio调试器下运行,则调试器将在DoWork事件处理程序中引发未处理异常的点处中断。 我希望有时会抛出这些异常,并且希望在RunWorkerCompleted事件中而不是在DoWork中处理它们。 我的代码正常运行,并且在RunWorkerCompleted事件中正确处理了错误,但是我终生无法弄清楚如何停止抱怨“未处理的异常”的.NET错误对话框。 BackgroundWorker是否应该自动捕获该错误? 这不是MSDN文档说明的吗? 我需要做什么做的通知.NET,这个错误被处理
  • 如何等待 BackgroundWorker 完成然后退出控制台应用程序(How to wait for BackgroundWorker to finish and then exit console application)
    问题 我已经编写了一个示例控制台应用程序来使用 Stackoverflow 中发布的示例之一来测试后台工作人员。 我有一个后台工作程序,它以 main 方法开始,但如果我按 Enter 键,它会在操作中间结束,因为我在 main 方法中编写了一个 console.readkey。 但我希望它等到后台工作人员完成工作然后退出应用程序。 这是我的代码。 class Program { private static BackgroundWorker worker = new BackgroundWorker(); private event EventHandler BackgroundWorkFinished; static void Main(string[] args) { worker.DoWork += worker_DoWork; worker.RunWorkerCompleted += worker_RunWorkerCompleted; worker.ProgressChanged += worker_ProgressChanged; worker.WorkerReportsProgress = true; worker.WorkerSupportsCancellation = true; Console.WriteLine("Starting Application
  • 如何正确等待BackgroundWorker完成?(How to wait correctly until BackgroundWorker completes?)
    问题 观察以下代码: var handler = GetTheRightHandler(); var bw = new BackgroundWorker(); bw.RunWorkerCompleted += OnAsyncOperationCompleted; bw.DoWork += OnDoWorkLoadChildren; bw.RunWorkerAsync(handler); 现在,假设我要等到bw完成工作。 正确的做法是什么? 我的解决方案是这样的: bool finished = false; var handler = GetTheRightHandler(); var bw = new BackgroundWorker(); bw.RunWorkerCompleted += (sender, args) => { OnAsyncOperationCompleted(sender, args); finished = true; }); bw.DoWork += OnDoWorkLoadChildren; bw.RunWorkerAsync(handler); int timeout = N; while (!finished && timeout > 0) { Thread.Sleep(1000); --timeout; } if (!finished) {
  • Why does the inner exception reach the ThreadException handler and not the actual thrown exception?
    I'm seeing some wierd behaviour when throwing exceptions and catching them in the Application.ThreadException event handler. Basically whats happening in the sample below is that an exception is thrown in the DoWork event handler of a BackgroundWorker. The RunWorkerCompleted event handler rethrows a new exception with the original as the inner exception. Why does the inner exception show up in the ThreadException event handler and not the acutal exception being thrown? If I do not provide an inner exception in the RunWorkerCompleted event handler, the correct exception will show up. using
  • How does BackgroundWorker decide on which thread to run the RunWorkerCompleted handler?
    I am trying to figure out how BGW decides which thread to run the RunWorkerCompleted handler when its work is done. My initial test uses a WinForm application: On the UI thread, I start bgw1.RunWorkerAsync(). Then I tried to start bgw2.RunWorkerAsync() through bgw1 in 2 different places: bgw1_DoWork() method or bgw1_RunWorkerCompleted() method. My initial guess is BGW should remember which thread it is started on and return to that thread to execute the RunWorkerCompleted event handler when its work is done. But the test result is strange: Test 1 If I start the bgw2.RunWorkerAsync() in bgw1
  • 自己班级的背景工作者处理(Backgroundworker processing in own class)
    问题 好吧,我遇到以下问题,希望您能对我有所帮助: 我想创建一个带有后台工作程序的WPF应用程序,以更新Richtextboxes和其他UI元素。 该后台工作人员应处理一些数据,例如处理文件夹的内容,进行一些解析等等。 由于我想将尽可能多的代码移到Main类之外,因此我在下面创建了一个名为MyProcess.cs的类(实际上,到目前为止该类没有多大意义,它将填充更多内容)处理元素(如果此问题已解决)。 常规功能应为: MainWindow:将创建一个字符串数组(名为this.folderContent ) MainWindow:后台工作者开始使用此数组作为参数 MainWindow:将调用DoWork()方法(我知道,该方法现在在新线程中运行) MyProcess:根据给定的字符串数组生成一个(迄今为止未格式化的)段落 MainWindow:如果后台工作人员完成,则调用RunWorkerCompleted()方法(在UI线程中运行),该方法应通过该方法的return参数更新WPF RichTextBox。 最后一步会导致InvalidOperationsException,并带有以下注释:“调用线程无法访问此对象,因为另一个线程拥有它。” 我读了一些有关后台工作者类及其功能的文章。 所以我认为这与this.formatedFilenames.Inlines.Add(new Run(
  • Cancelling a BackgroundWorker
    My DoWork for backgroundworker1 sets a wait timer via a class WakeUp, it works well. The problem right now is that sometimes my while CancellationPending is ending in an infinite loop. So inside DoWork there is a call to WaitOne, the DoWork sets the wait timer stuff and waits the thread until the timer triggers. I need for the BackgroundWorker to shut down right away but as well I have to keep a reference to BackgroundWorker so I can keep track of each alarm in my program. Why is CancellationPending taking so long? It never seems to finish. Is there some way to kill off the backgroundworker
  • WPF Dispatcher.BeginInvoke 和 UI/后台线程(WPF Dispatcher.BeginInvoke and UI/Background Threads)
    问题 我想我需要一些关于 WPFs Dispatcher.Invoke和Dispatcher.BeginInvoke用法的说明。 假设我有一些长时间运行的“工作”代码,例如在简单的 WPF 应用程序中按下按钮时调用的代码: longWorkTextBox.Text = "Ready For Work!"; Action workAction = delegate { Console.WriteLine("Starting Work Action"); int i = int.MaxValue; while (i > 0) i--; Console.WriteLine("Ending Work Action"); longWorkTextBox.Text = "Work Complete"; }; longWorkTextBox.Dispatcher.BeginInvoke(DispatcherPriority.Background, workAction); 此代码在执行workAction 时锁定了我的用户界面。 这是因为 Dispatcher 调用总是在 UI 线程上运行,对吗? 假设这一点,配置我的调度程序以在与我的 UI 不同的线程中执行workAction的最佳实践是什么? 我知道我可以在我的workAction 中添加一个BackgroundWorker来防止我的 UI
  • 正确处理BackGroundWorker的方法(Proper way to Dispose of a BackGroundWorker)
    问题 这是处置BackGroundWorker的正确方法吗? 我不确定是否有必要在调用.Dispose()之前删除事件。 还可以在RunWorkerCompleted委托内调用.Dispose()吗? public void RunProcessAsync(DateTime dumpDate) { BackgroundWorker worker = new BackgroundWorker(); worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted); worker.DoWork += new DoWorkEventHandler(worker_DoWork); worker.RunWorkerAsync(dumpDate); } void worker_DoWork(object sender, DoWorkEventArgs e) { // Do Work here } void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { BackgroundWorker worker = sender as BackgroundWorker; worker
  • 取消BackgroundWorker(Cancelling a BackgroundWorker)
    问题 我的backgroundworker1 DoWork通过WakeUp类设置了一个等待计时器,效果很好。 现在的问题是,有时我的while CancellationPending会以无限循环结束。 因此,在DoWork内部有一个对WaitOne , DoWork设置了等待计时器的内容并等待线程直到计时器触发。 我需要立即关闭BackgroundWorker ,但同时也必须保留对BackgroundWorker的引用,以便可以跟踪程序中的每个警报。 为什么CancellationPending花费这么长时间? 它似乎永远不会完成。 有什么方法可以杀死backgroundworker而不必在while循环中等待这么长时间吗? switch (alarmNum) { case 1: WakeUp.CancelWakeUp(threadHandles[removal]); if(backgroundworker1.IsBusy) { backgroundworker1.CancelAsync(); } while(backgroundworker1.CancellationPending) { } backgroundworker1.RunWorkerAsync(); break; case 2: if (backgroundworker2.IsBusy) {