Monday, November 3, 2008

Multithreading and WPF Windows

I recently had an interesting problem come up with UI threads in .NET applications and how input is processed. Here's the basic rundown...

A user initiates an event in the UI on Thread 1

The UI makes a call to some business layer to process the request. Note, this MAY or MAY NOT continue to execute on the UI thread.

The business logic needs more information before it can proceed. Nicely separated, the business thread raises an event called GetAdditionalInfo. Now, the application can either provide the information directly back to to the business logic or it may need to ask the user.

Now, here's where things get "interesting". If the business logic is running on a different thread, you've got a problem. You can't simply create a new WPF window to ask the user for the information. If you try, you'll get the following error message:

System.InvalidOperationException: The calling thread must be STA, because many UI components require this.

Well, what are you to do? A few options come up.

One option is to put a message onto the dispatch thread for the current window. While this method will definitely get your message across, you have a bit of a problem. That is, the business logic thread will continue to execute Problem is, you need to hold up the business logic thread until the user has responded to the question.

The trick lies in spawning off a new thread, creating the WPF window in that new thread, and block the business logic thread until the UI finishes. Of course, if the business logic thread is already a UI thread, this isn't needed and you can just do a ShowDialog directly. Code below...



private static void ShowWPFDialog(object arg)
{
GetAdditionalInformationWPFWindow myWindow = new GetAdditionalInformationWPFWindow();

// ShowDialog will block the calling thread..
myWindow.ShowDialog();

// Presumably at this point you have the data required from the UI... So you could set it in the arguments..
((BusinessRequestEventArgs)args).MyResult = myWindow.BusinessLogicResult;
}

private static void GetAdditionalInformation(object sender, BusinessRequestEventArgs e)
{
// If the current thread is already an STA thread we can just run on this thread.
if (Thread.CurrentThread.ApartmentState == ApartmentState.STA)
RetrieveSecondaryLogon(arg);
else
{
Thread _UIThread;
_UIThread = new Thread(new ParameterizedThreadStart(RetrieveSecondaryLogon));
_UIThread.SetApartmentState(ApartmentState.STA);
_UIThread.Start(arg);
// Block the caller until the UI thread ends..
_UIThread.Join();
}
}

No comments:

Post a Comment