Thursday, June 21, 2007

Making cross-thread calls / events

Many developers first introduction to multithreaded programming is the classic challenge of using one or more "background-worker" threads to do some work thats expected to take longer than the average user wants to wait for his windows application to become responsive again.
Making a worker-method and starting up a thread to run it - or asking the ThreadPool to assign the method to a thread from the pool (QueueUserWorkItem) is quite simple - but soon after the coding starts to get interesting (and fun!).

Now you have to worry about using Mutex and Monitor, etc. to ensure that there's no sharing violation between ressource that the threads share. This in itself is a worthy topics of several books and many blogposts (a lot better than my humble abilities allow me to write).
In a simple scenario as described above you might be able to avoid many of these problems if you contain all the necessary data within each worker thread - but judging on the number of times I've been asked about this, you still encounter yet another issue: cross-thread communication.
Imagine that you've started up your worker-thread and it works happily, enjoying as many cpu-cycles as your operating system allows it, while still letting the main application thread provide a responsive UI. Then you'll at some point start to wonder "Okay....so now my fingamaboob is doing some work...thats nice...I wonder how far it's gotten".
- "No problem", I hear you say. "I'll just have my worker thread output it's status to the window running in the main thread."
This approach will often lead to one of two scenarios:


  1. InvalidOperationException, Cross-thread operation not valid

  2. Some weird construction with a shared status variable, that the UI is polling every X seconds

The correct solution to this problem is to use a proper cross-thread call. For instance you can use Invoke (or BeginInvoke if you're the asynchroneous type). All Windows Forms controls has an Invoke method that you can call and provide with a delegate and a set of parameters. That way you are instructing the thread that "owns" the control to run the call the delegate with the specified parameters.

As you can see below it can be done quite elegantly using anonymous methods and a custom delegate.


public partial class Form1 : Form

{

public Form1()

{

InitializeComponent();

}



private delegate void ReportStatusHandler(string status);



private void DoBoringWork(object param)

{

for (int i = 0; i < 10000; i++)

{

//Simulate boring work

Thread.Sleep(10);

if (0 == (i % 100))

{

//Output status for every 100



//WRONG:

//listBox1.Items.Add(i.ToString()+" items processed");



//Right:

ReportStatusHandler rsh = new ReportStatusHandler(

delegate(string s) {

listBox1.Items.Add(s);

});

listBox1.Invoke(rsh,

i.ToString() + " items processed");

}

}

}



private void button1_Click(object sender, EventArgs e)

{

//Put work in Queue to be done by ThreadPool

ThreadPool.QueueUserWorkItem(

new WaitCallback(DoBoringWork), null);

}

}

2 comments:

jRad said...

I know its been a while since you posted this, but I would think that the most correct solution would be to implement the event-based async pattern here. .NET already has lots of support for it and then you don't have to tie your worker thread directly to the UI.

Pelo Steward said...

Thank you for sharing your article, it's helpful.

barcodes for .net