Friday 24 July 2009

To make the life in the multithreaded world easier 2

As I mentioned in my To make the life in the multithreaded world easier 1, I kept writing code as below to check the "InvokeRequired" property to check if the method is invoked from a worker thread or the main thread.
private void DoSomethingFunction()
{
if(this.InvokeRequired)
{
this.BeginInvoke((System.Threading.ThreadStart)delegate()
{
DoSomethingFunction();
});
return;
}
// Do something here
}
After doing the check again and again, I started to ask myself - is there a way to abstract it a bit so that I don't need to repeat myself?

And this is what I come up with - a class acts as a smart agent:
Public class AsyncCall
{
public static void AutoBeginInvoke(System.Windows.Forms.Control targetControl, Delegate targetMethod)
{
if (targetControl.InvokeRequired)
targetControl.BeginInvoke(targetMethod);
else
targetMethod.DynamicInvoke(null);
}
}
This method checks to see if this method is invoked from a worker thread or from the main thread where the windows control is created, and invoke the method accordingly.

Our DoSomethingFunction() can be rewritten as:
private void DoSomethingFunction()
{
AsyncCall.AutoBeginInvoke(this, (System.Threading.ThreadStart)delegate(){// Do something here});
}
Let's have a more concrete example, say I want to update a textbox or a label in a thread safe way, in the past, I have to do things like this:
private void UpdateTextBox()
{
if(textBox1.InvokeRequired)
{
textBox1.BeginInvoke((System.Threading.ThreadStart)delegate()
{
UpdateTextBox();
});
return;
}
textBox1.Text = "Update test";
}

private void UpdateLabel()
{
if(lbl1.InvokeRequired)
{
lbl1.BeginInvoke((System.Threading.ThreadStart)delegate()
{
UpdateLabel();
});
return;
}

lbl1.Text = "Update test";
}
Now I simply call:
private void UpdateTextBox()
{
AsyncCall.AutoBeginInvoke(textBox1, (System.Threading.ThreadStart)delegate(){textBox1.Text = "Update test"});
}

private void UpdateLabel()
{
AsyncCall.AutoBeginInvoke(lbl1, (System.Threading.ThreadStart)delegate(){lbl1.Text = "Update test"});
}
Some people may want to separate the anonymous part
(System.Threading.ThreadStart)delegate(){ //do update}
into a different method.But I found it quite nice and neat here.

What if I want to pass parameters to my update method? Then some delegate accepting parameters have to be created.

You can have a delegate defined as this:

public delegate void SomeDelegate(string args);

Then add a new method in AsyncCall class:
public static void AutoBeginInvoke(System.Windows.Forms.Control targetControl, SomeDelegate targetMethod, string args)
{
if (targetControl.InvokeRequired)
targetControl.BeginInvoke(targetMethod, args);
else
targetMethod.DynamicInvoke(args);
}
This is your new UpdateTextBox method:
private void UpdateTextBox()
{
AsyncCall.AutoBeginInvoke(textBox1, (SomeDelegate)delegate(string args){textBox1.Text = args;}, "update test");
}
What if I want to do a search, and after getting the search result back I want to do something else.

Normally, we would have:
public ILIst delegate SomeSearchDelegate();

private IList Search()
{
IList result = null;

// Do search here...

return result;
}

private void OnSearchCompleted(IAsyncResult ar)
{
SomeSearchDelegate del = ar.AsyncState as SomeSearchDelegate;

IList result = del.EndInvoke(ar);

// Do some post search processing...
}

private void SomeSearchMethod()
{
SomeSearchDelegate del = new SomeSearchDelegate(Search);
del.BeginInvoke(OnSearchCompleted, del);
}
To do the task using AsyncCall class, we can define another method in the AsyncCall class:
public static void AutoBeginInvoke(SomeSearchDelegate targetMethod, AsyncCallback callbackMethod)
{
targetMethod.BeginInvoke(callbackMethod, targetMethod);
}
So the SomeSearchMethod() can be rewritten as:
private void SomeSearchMethod()
{
AsyncCall.AutoBeginInvoke(
(SomeSearchDelegate)delegate()
{
IList result = null;
// Do search here...
return result;
},
(AsyncCallback)delegate(IAsyncResult ar)
{
SomeSearchDelegate del = ar.AsyncState as SomeSearchDelegate;
IList result = del.EndInvoke(ar);
// Do some post search processing...
});
}

Hope what discussed above is not as clear as mud to the readers ;)...

No comments: