Browse DevX
Sign up for e-mail newsletters from DevX


Threading Support in the .NET Framework : Page 4

To provide a great user experience applications need to do a lot of thingsmany of them seemingly at the same time. Professional applications do this using multi-threading. Until recently however, creating true multi-threaded applications wasn't a simple task and wasn't available to most developers. Thankfully, multi-threading is a fundamental feature of the .NET Framework and developers can write multi-threaded applications with relative ease, no matter whether they use managed C++ code, C#, VB.NET, or any other .NET language.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

Threading and Windows Forms
One of the issues that surprises most developers is that .NET Windows Forms are not thread-safe. It is not safe to call a method on a Windows form or control from a secondary thread. Although this may seem strange at first, as a developer you actually want this. Thread-safety doesn't come for free! If Windows Forms were programmed in a thread-safe way, all single threaded applications would pay a hefty performance penalty.

Does that mean Windows Forms applications cannot be used in a multi-threaded environment? No! It simply means that the architect of a multi-threaded Windows Forms application has to worry about thread-safety himself.

Let's look at an example. Consider the code in Listing 3—a very simple Windows Form with a DataGrid control. When the form loads, the LoadCustomers() method retrieves customer information from a Web service (I did not include the Web service in my example). Once you have the DataSet, you use the DisplayCustomers() method to display the customers in the DataGrid.

The problem with this example is that it may be slow to load large customer lists from a Web service, and you don't want the application to appear hung while the download is in progress. You know that you can easily fix this problem by spinning up a new worker thread for the LoadCustomers() method:

private void Form1_Load(object sender, System.EventArgs e) { ThreadStart oTS = new ThreadStart(this.LoadCustomers); Thread oThread = new Thread(oTS); oThread.Start(); }

Here you're creating a separate thread for the LoadCustomers() method and starting the thread right away, which performs a background download of the data while the main form is already operational. Once LoadCustomers() completes it's work it calls DisplayCustomers() to update the grid. At least this sounds OK. However, when you try to run this application you'll generate the following exception on the line of code that attempts to set the DataSource of the DataGrid:

"An unhandled exception of type 'System.ArgumentException' occurred in system.windows.forms.dll. Additional information: Controls created on one thread cannot be parented to a control on a different thread."

You tried to interact with a Windows Forms object from a thread other than the one that created the control (DataGrid in this example). So how do you hand control back over to the first thread? You use the Windows Forms control Invoke() method. It allows execution of any method on the form's or control's main thread. Invoke() expects a Delegate (see sidebar) as well as a list of arguments (parameters). Since Invoke() has to be generic it cannot know the number of parameters to expect. Therefore, you'll pass the parameters as an array.

You'll now add another method called DisplayCustomer_TS() to the form class that allows you to update the grid in a thread-safe fashion. You'll use DisplayCustomers_TS() not to update the grid directly but to call the original DisplayCustomers() method in a thread-safe manner:

private void DisplayCustomers_TS( DataSet dsCustomers) { DisplayCustomersDelegate oDel = new DisplayCustomersDelegate( this.DisplayCustomers); DataSet[] args = {dsCustomers}; this.Invoke(oDel,args); } delegate void DisplayCustomersDelegate( DataSet dsCustomers);

The first line instantiates a delegate that points to your original DisplayCustomers() method. (Note that the last line in this snippet defines the delegate.) You then create an array of DataSet objects with a single item (your customer DataSet). Finally, you'll call the Invoke() method with the delegate and the DataSet in the array of arguments. If you aren't familiar with Delegates this may look a bit confusing at first but it is really quite straightforward. Simply point the Invoke() method at another method and say, "See that method? Go ahead and run it for me..."

That does the trick! The only thing that's left to do is make the LoadCustomers() call DisplayCustomers_TS() instead of DisplayCustomers(). Now your little application is perfectly thread-safe! Not surprisingly, the Visual Basic .NET implementation of this solution is rather similar:

Private Sub DisplayCustomers_TS( _ ByVal dsCustomers As DataSet) Dim oDel As New _ DisplayCustomersDelegate( _ AddressOf Me.DisplayCustomers) Dim args() As DataSet = {dsCustomers} Me.Invoke(oDel, args) End Sub Delegate Sub DisplayCustomersDelegate( _ ByVal dsCustomers As DataSet)

Thread Properties and Methods
So far, you've created new threads and started them. However, you can do more with threads. For instance, we can adjust a thread's priority:

oThread.Priority = ThreadPriority.BelowNormal

.NET let's you fine-tune how much processing time each thread will get. There are a number of other things you can do with a thread such as give it a friendly name or query an internal thread ID for identification. You can also suspend, abort, or continue a thread, and much more. For more information on this subject, please refer to the .NET Framework SDK documentation.

Threads Everywhere
Now that you know threads are relatively easy to use in .NET, shouldn't you use threads everywhere to improve performance? No! Threads do not improve performance. They make a system more responsive by creating the illusion of performing multiple tasks at once rather than putting one process on hold while it performs another. All threads taken together have the same processing power as before (unless the application runs on a multi-processor machine, which can truly execute two things at the same time). The system must use some of the available processing time for the thread switching operations making total performance worse than before. Don't underestimate this overhead! In fact, if you spin up a lot of threads, switching between the threads may take up more time than running the threads. (See Sidebar: The Thread Pool)

Therefore, you want to use threads carefully. The Windows Forms example above demonstrated a very good use for a thread; you used a secondary thread for a process that could potentially be very slow and make your interface appear hung. I can imagine a scenario where it could take minutes to download a large customer list from a Web service. After 15-30 seconds (maybe even less than that), I would expect the user to get somewhat concerned about the state of the application if the main window entirely fails to refresh beyond showing the title bar. Having a non-responsive application not only annoys the user and could guide them toward terminating the entire process through CTRL-ALT-DEL, it also looks very unprofessional.

Markus Egger is president of EPS Software Corporation, located in Houston, Texas. He is also the founder of EPS Software Austria, located in Salzburg. He concentrates on consulting in COM-based, object-oriented development and Internet applications. He is an international author and speaker, and is co-publisher of Component Developer Magazine. He is also the author of "Advanced Object-Oriented Programming with Visual FoxPro," from Hentzenwerke Publishing. For the past several years, Markus has received the Microsoft MVP award. Several applications he has worked on (mostly as project manager) have received Microsoft Excellence Award nominations. He is the author of several tools, such as GenRepoX (public domain), the Fox Extension Classes, and Visual WebBuilder. A full bio can be found on the web at: www.eps-software.com/MarkusEgger. .
Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date