Reputation: 136643
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
SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext(control.Dispatcher));
.
for (int i = 0; i < 3; i++)
{
control.Dispatcher.Invoke(DispatcherPriority.Background,
new Action(delegate { }));
Thread.Sleep(50);
}
Upvotes: 7
Views: 2185
Reputation: 21
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);
Upvotes: 0
Reputation: 1209
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.
Upvotes: 0
Reputation: 14682
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.
Upvotes: 10
Reputation: 7058
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.
Upvotes: 3