BackgroundWorker does not fire the RunWorkerCompleted event

G

Guest

I am trying to unit test a control that uses a background worker component
and have discovered that the RunWorkerCompleted event does not fire in this
scenario. The documentation doesn't say anything about this behavior, and I
was wondering if anyone here could tell me why the RunWorkerCompleted event
does no fire in the following sample code:

using System;
using System.Threading;
using System.Windows.Forms;
using System.ComponentModel;

namespace BackgroundWorkerPrototype
{
class Program
{
static void Main( string[] args )
{
BackgroundWorkerUserControl control = new
BackgroundWorkerUserControl();
control.StartBackgroundWorker();
Thread.Sleep( 3000 );
}
}

class BackgroundWorkerUserControl : UserControl
{
private BackgroundWorker worker;

public BackgroundWorkerUserControl()
{
worker = new BackgroundWorker();
SuspendLayout();
worker.DoWork += new DoWorkEventHandler( worker_DoWork );
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
worker_RunWorkerCompleted );
}

public void StartBackgroundWorker()
{
worker.RunWorkerAsync();
}

private void worker_DoWork( object sender, DoWorkEventArgs e )
{
Console.WriteLine( "DoWork started." );
Thread.Sleep( 1000 );
Console.WriteLine( "DoWork completed." );
}

private void worker_RunWorkerCompleted( object sender,
RunWorkerCompletedEventArgs e )
{
Console.WriteLine( "RunWorkerCompleted called." );
}
}
}

Thanks,
John
 
G

Guest

Ok, I think I know what is going wrong here. To troubleshoot this, I ended up
implementing my own code to handle the long running thread asyncronously. I
did this by defining a custom delegate and invoking it asyncronously. Here's
the method that I'm calling asyncronously:

private void LoginUser( User user )
{
LoginUserCompletedDelegate completedDelegate =
new LoginUserCompletedDelegate( LoginUserCompleted );
AuthenticationResult result = AuthenticationResult.BadCredentials;

try
{
result = _authenticationGateway.Authenticate( user );

// initial exception here
this.Invoke( completedDelegate, null, result, user );
}
catch( Exception e )
{
// another exception here
this.Invoke( completedDelegate, e, result, null );
}
}

In this method, I'm trying to ensure that if any exceptions occur, they get
passed back to the UI thread. The BackgroundWorker component also provides
this functionality, and is very likely using Control.Invoke or
Control.InvokeAsync to do it. Trouble is, those methods are not supported if
you instance a control directly, as I was attempting to do in my tests.

So, in the code above, the first Invoke call results in an exception, which
then get handled my the catch block. Then, the Invoke call in the catch block
results in a new exception. Finally, in my EndInvoke method (listed below), I
try to call Invoke again, with similar results:

private void LoginUserEndInvoke( IAsyncResult result )
{
LoginUserCompletedDelegate completedDelegate =
new LoginUserCompletedDelegate( LoginUserCompleted );
try
{
LoginUserDelegate loginUserDelegate =
(LoginUserDelegate)result.AsyncState;

// yet another new exception here
loginUserDelegate.EndInvoke( result );
}
catch( Exception e )
{
// yet another new exception here
this.Invoke( completedDelegate, e,
AuthenticationResult.BadCredentials, null );
}
}

The resulting exception looks like this:

Thread Name: <No Name>
System.InvalidOperationException: Invoke or BeginInvoke cannot be called on
a control until the window handle has been created.
at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate
method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
at MyProject.Controls.LoginControl.LoginUserEndInvoke(IAsyncResult
result) in C:\MyProject.Controls\LoginControl.cs:line 139
at
System.Runtime.Remoting.Messaging.AsyncResult.SyncProcessMessage(IMessage msg)
at
System.Runtime.Remoting.Messaging.StackBuilderSink.AsyncProcessMessage(IMessage msg, IMessageSink replySink)
at
System.Runtime.Remoting.Proxies.AgileAsyncWorkerItem.ThreadPoolCallBack(Object o)
at System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object
state)
at System.Threading.ExecutionContext.Run(ExecutionContext
executionContext, ContextCallback callback, Object state)
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object
state)

I suspect that the BackgroundWorker component is using Control.Invoke to
implement it's RunWorkerCompleted event, and it silently absorbs the
exception above. This isn't great behavior, and if they would have simply let
the exception bubble up, it would have saved me a day and a half of work.
Perhaps someone will change this behavior in a later version of the
framework. Now to figure out how to unit test winforms ui code...
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top