Sunday, November 07, 2004 3:00 AM bart

About IAsyncResult, Invoke, BackgroundWorker and much more

Yesterday, I had a meeting with some guys who're working on a .NET-based development project that covers quite some stuff: database access, web service layers (facade pattern), smart device applications and WinForms clients. Although rather basic, still interesting stuff to see people walking the first couple of .NET-miles. One of the problems they came across was a multi-threading problem. As the matter in fact, it was partially my fault since I recommended them to call their webservices asynchronously in the Windows Forms application (and Smart Device application). So they did. However, what they did as well was performing a UI update inside the callback method of the web service call. Let's illustrate with a simple sample:

private void button1_Click(object sender, System.EventArgs e)
{
  
localhost.Service1 svc =
new localhost.Service1();
  
svc.BeginGetProducts(
new AsyncCallback(ProductsFinished), svc);
}

private void ProductsFinished(IAsyncResult res)
{
  
localhost.Service1 svc = (localhost.Service1) res.AsyncState;
  
DataSet ds = svc.EndGetProducts(res);
  
//UI updates
}

It seems to be correct but it isn't. Instead of performing UI-updating logic directly inside the ProductsFinished method, you should propagate this back to the UI-thread by using the Invoke method. Of course some modifications to the skeleton of the code are required in order to gain access to the retrieved data etc, but basically it looks like this:

private void button1_Click(object sender, System.EventArgs e)
{
  
localhost.Service1 svc =
new localhost.Service1();
  
svc.BeginGetProducts(
new AsyncCallback(ProductsFinished), svc);
}

private delegate void UpdateUI(DataSet ds);

private void ProductsFinished(IAsyncResult res)
{
  
localhost.Service1 svc = (localhost.Service1) res.AsyncState;
  
DataSet ds = svc.EndGetProducts(res);
   this.Invoke(new UpdateUI(ProductsFinishedUpdate), new object[] { ds });

}

private void ProductsFinishedUpdate(DataSet ds)
{
   //UI updates
}

Another problem they were faced with was a weird (but obvious) non-deterministic behavoir. Again it was caused by my "asynchronous web services calls recommendation". Guess what happens if you're doing this?

private void Form1_Load(object sender, System.EventArgs e)
{
   localhost.Service1 svc = new
localhost.Service1();
   svc.BeginGetCategories(new
AsyncCallback(CategoriesFinished), svc);
   svc.BeginGetProducts(new AsyncCallback(ProductsFinished), svc);
}

and you rely on the assumption that CategoriesFinished will complete before ProductsFinished? Indeed, non-deterministic behavior (and of course this happens the very first time during a demo you're giving :-)). Actually, what they told me was that it did not work the first time they did a build of the solution but the second time it worked well. That really did sound strange to me (I didn't see the code yet) but as an afterthought (now it's solved of course) I was thinking that the web service project was part of the solution and thus the ProductsFinished occurred before the CategoriesFinished because of the compile delay of the first hit on the web application. CategoriesFinished has far less processing to do, so that's the only reason I can think of (combined with the fact that the problem only occurred during the first debugging cycle after a rebuild step).

It's also worth to mention that the .NET Framework v2 will have support for this kind of things by means of the System.ComponentModel.BackgroundWorker class (component) that can be used in all kinds of applications to do background work. I've been experimenting with this last week in depth and I hope this component will help people to solve problems like this (although it doesn't help to learn the basics of threading to newbies). For me, it's a welcome gift since I'm using this kind of things regularly (cf. the SchoolServer project that contains quite some wizards with a progress-bar that reflects background work).

Back to the project I was assisting on today. I decided to show them the use of a typed DataSet on the web service layer and how to link two "tables" (i.e. elements in DataSet terminology) using a relation and what the impact of this is on the code that xsd.exe (through Visual Studio .NET of course) is spitting out. I took me a bit to convince the guys of the usefulness of a typed DataSet (code generation, easy iteration, easy rows manipulation, parent-child data retrieval methods, etc) and the demonstration resulted in moving around the typed DataSet throughout the solution (from the web services layer to a separate class project to the DAL) to get a strong n-tiered model in the end. This made me think again of the power of Visual Studio .NET to copy-paste files between projects within a solution but with one big remark: don't forget to modify the namespace in the associated code-file (something that newbies often forget as I saw dozens of times before). Another great point is the power behind the XSD deferring in the Add Web Reference dialog. When adding a web reference to a web service that returns a typed dataset, the dataset is reconstructed client-side as part of the generated proxy (therefore avoiding the need to have a shared class client-side and server-side to share the common types). It was however the first time I tried this in a smart device application and guess o what ... the typed dataset was added on the .NET Compact Framework application as well (although the .NET CF doesn't provide support for typed datasets through the use of the Visual Studio tools or xsd.exe). I still have to check the richness of this workaround (if you can call it like that) but it seems to be great at first sight (instead of having to use xsdcf.exe, which is a 3rd party tool that can be downloaded from the net http://www.tmgdevelopment.co.uk/xsdcf.htm - please take a look at the known bug since I came across this issue a couple of times already).

And last but not least: if you have to update the Url of a web service reference, please do it using the Properties and not in the code behind files of the web reference (i.e. the generated proxy class) since the latter is always reconstructed when updating the web reference. I saw this "problem" (which is behavior by design, I can't think of another way to avoid this when designing tools like wsdl.exe) popping up again today. The URL was changed in the proxy class a couple of weeks ago but my change was discarded of course since the guys in question performed a web reference refresh and thus they were trying to use http://localhost on a pocket pc application (in the emulator). Unfortunately, we couldn't debug this problem completely since name resolution of a NetBIOS name did fail in the emulator (the NIC was disconnected, thus a dynamic IP wasn't available as well). I'm trying to find a workaround for this debugging trouble for smart device applications when you're on the road. If anyone has a suggestion, please drop me a line.

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under:

Comments

# re: About IAsyncResult, Invoke, BackgroundWorker and much more

Sunday, November 07, 2004 8:02 PM by bart

They also could use the .WaitAny() or WaitAll() methods to tackle the 'non-deterministic' behaviour...

# re: About IAsyncResult, Invoke, BackgroundWorker and much more

Thursday, August 04, 2005 4:55 PM by bart

I don't see WaitAny() or WaitAll() in the compact framework or smart device environment. Where does one look?