Thursday, November 01, 2012

C# Windows Forms - Calling Main Form Methods from A Thread

I am a bit embarrassed to say that I don't write much Windows Forms code these days since most of my work are done as web based apps. But, I sometimes need to whip up some desktop apps that are simple quick to do DB operations and such.

Desktop apps can provide very interactive user expriences without resorting to complex JavaScript codes. To this effect the use of threads in Windows forms code is pretty much a must to update the form status etc., live. But how to tie these threads to the main UI thread is often a very puzzling experience, especially if you don't do this very often or coming from the ASP.NET world.

People who do not understand various mufti-threading issues like memory areas and concurrency go ahead and call methods in the main thread directly from sub-threads. This works probably half the times if lucky and other times, the program crashes due to some memory violation.

Anyhow, I do not do these often enough to the point that I can't remember how to do this right off the top of my head, so here I am going to write this article for myself here so the next time I have to do it, I can come back here and remembered how to do it.

I am going to include a Visual Studio 2010 project containing the prototypical clock update app. This app updates the clock time on the main form from the thread that's running every 100 ms. This also has the thread stop, abort and cleanup part too.

The Example ZIP is HERE.

Some Key Concepts
  • You need to declare a Delegate in your class. This is simply a template that models a function call. I made a mistake of trying to use this as the instance to the method. Don't think too hard about it. It is kind of like what you do in the .H file in C++ The delegate becomes a Type just like String and Double.
  • Knowing that the Delegate is just a type (i.e., you cannot call it directly), you would want to declare the instance of it next in the (main form) class. I would do both the delegate and declaration of the actual instance right next to each other. 
  • Then within a class constructor, assign the Entry Point of the actual function to the delegate instance.
  • In the thread that wants to call a function in the main thread, use the Main Form's Invoke method. This means that you would pass the Main Form object to the thread, and the thread uses MainForm.Invoke method supplying its delegate instance and parameters if any. The key here is that the actual Main Form object is passed on to the thread object and that the Invoke method out of the main form is called. This is the part which is not very intuitive to me, since it is the MainForm object in the main thread after all, so it cannot be so clear to me what can actually be mucked with directly from the thread.
  • Also note that Invoke() does NOT have to come from the Main Form. Any controls that are contained in the main form appears to be a fair game and since all the controls have the Invoke method, you can use the same technique in the example against some control object (or subclass of thereof).
Once you understand and get a hang of it, you can expand it in more complex ways like doing this against subforms, or subclasses of controls etc.

Try it and let me know what you think!

No comments: