Friday, August 14, 2009

Calling methods on an ActiveX control from Managed Code/.NET

.NET 2.0 introduced BackgroundWorker. I have come to love and rely on this little gem to maintain UI responsiveness while a long running task executes in the background.

What happens if the long running task that needs to execute in the background must run in a Single Threaded Apartment? This might happen if you're doing image processing and the library that handles the heavy lifting is an ActiveX control.

Since it has a visual representation it must run in a Single Threaded Apartment. But BackgroundWorker does it's background work on a thread that does not run in a Single Threaded Apartment.

To address this I wrote a shameless rip of BackgroundWorker. The only significant difference is that it does its background work on a thread that runs in a single threaded apartment.

Hopefully the .NET devs won't mind. Imitation is the sincerest form of flattery.

A colorized and formatted version of the code below for StaBackgroundWorker can be found here.
/// <summary>    
/// Similar to BackgroundWorker except that it does its work on a Single Threaded Apartment thread.
/// </summary>
public class StaBackgroundWorker
{
public event System.ComponentModel.DoWorkEventHandler DoWork;
public event System.ComponentModel.ProgressChangedEventHandler ProgressChanged;
public event System.ComponentModel.RunWorkerCompletedEventHandler RunWorkerCompleted;
private Control creatorControl;
public StaBackgroundWorker(Control creatorControl)
{
this.creatorControl = creatorControl;
}

public void RunWorkerAsync()
{
RunWorkerAsync(null);
}

public void RunWorkerAsync(object userState)
{
Thread staThread = new Thread(new ParameterizedThreadStart(RunWorkerAsyncThreadFunc));
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start(userState);
}

private void RunWorkerAsyncThreadFunc(object userState)
{
DoWorkEventArgs doWorkEventArgs = new DoWorkEventArgs(userState);
Exception doWorkException = null;

try
{
OnDoWork(doWorkEventArgs);
}
catch (Exception ex)
{
doWorkException = ex;
}

RunWorkerCompletedEventArgs workerCompletedEventArgs =
new RunWorkerCompletedEventArgs(doWorkEventArgs.Result, doWorkException, doWorkEventArgs.Cancel);

creatorControl.Invoke(new MethodInvoker(delegate() { OnRunWorkerCompleted(workerCompletedEventArgs); }));
}

protected virtual void OnDoWork(DoWorkEventArgs e)
{
if (DoWork != null)
DoWork(this, e);
}

private bool cancellationPending;
public bool CancellationPending
{
get { return cancellationPending; }
}

public void CancelAsync()
{
cancellationPending = true;
}

public void ReportProgress(int percentComplete, object userState)
{
ProgressChangedEventArgs e = new ProgressChangedEventArgs(percentComplete, userState); // marshal this call onto the thread that created the control that created us
creatorControl.Invoke(new MethodInvoker(delegate() { OnProgressChanged(e); }));
}

protected virtual void OnProgressChanged(ProgressChangedEventArgs e)
{
if (ProgressChanged != null)
ProgressChanged(this, e);
}

protected virtual void OnRunWorkerCompleted(RunWorkerCompletedEventArgs e)
{
if (RunWorkerCompleted != null)
RunWorkerCompleted(this, e);
}
}



No comments :

Post a Comment