+ All Categories
Home > Documents > Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18...

Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18...

Date post: 09-Jul-2020
Category:
Upload: others
View: 6 times
Download: 0 times
Share this document with a friend
24
699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers write more multithreaded code, a common set of scenarios and programming patterns for handling those scenarios emerges. The key scenarios relate to notifications of when a thread completes and notifications about the progress on the threads’ status. In addition, there is some built-in C# functionality for calling methods asynchronously, regardless of whether their signatures match ThreadStart. Most importantly, going with built-in patterns like this sig- nificantly reduces the effort in programming to solve these types of sce- narios from scratch. C 2 3 1 Multithreading Patterns Asynchronous Results Pattern Introduction Passing Data Completion Notification Passing Arbitrary State Background Worker Pattern Establishing the Pattern Exception Handling Windows Forms 0321533925_michaelis.book Page 699 Tuesday, July 29, 2008 3:47 PM
Transcript
Page 1: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

699

19

Multithreading Patterns

HAPTER 18 FOCUSED on managing threads and synchronizing thedata the threads share. As developers write more multithreaded

code, a common set of scenarios and programming patterns for handlingthose scenarios emerges. The key scenarios relate to notifications of whena thread completes and notifications about the progress on the threads’status. In addition, there is some built-in C# functionality for callingmethods asynchronously, regardless of whether their signatures matchThreadStart. Most importantly, going with built-in patterns like this sig-nificantly reduces the effort in programming to solve these types of sce-narios from scratch.

C

23

1

MultithreadingPatterns

AsynchronousResults Pattern

IntroductionPassing Data

Completion NotificationPassing Arbitrary State

BackgroundWorker Pattern Establishing the Pattern

Exception Handling

Windows Forms

0321533925_michaelis.book Page 699 Tuesday, July 29, 2008 3:47 PM

Page 2: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Chapter 19: Multithreading Patterns700

The two patterns specifically designed for these scenarios are the asyn-chronous results pattern and the background worker pattern, and thischapter investigates both. This chapter breaks down how to code thesepatterns into a step-by-step process, and points out some important char-acteristics of Windows Forms programming.

Asynchronous Results PatternMultithreaded programming includes the following complexities.

1. Monitoring the thread state for completion: This includes determining when a thread has completed, preferably not by polling the thread’s state or by blocking and waiting with a call to Join().

2. Passing data to and from the thread: Calling arbitrary methods asynchro-nously is cumbersome because they do not necessarily support ThreadState- or ParameterizedThreadStart-compatible signatures.

3. Thread pooling: This avoids the significant cost of starting and tearing down threads. In addition, thread pooling avoids the creation of too many threads, such that the system spends more time switching threads than running them.

4. Providing atomicity across operations and synchronizing data access: Add-ing synchronization around groups of operations ensures that opera-tions execute as a single unit and that they are appropriately interrupted by another thread. Locking is provided so two different threads do not access the data simultaneously.

5. Avoiding deadlocks: This involves preventing the occurrence of dead-locks while attempting to protect the data from simultaneous access by two different threads.

To deal with this complexity, C# includes the asynchronous results pat-tern. This section demonstrates how to use the asynchronous results pat-tern and shows how it simplifies at least the first three complexitiesassociated with multithreading.

0321533925_michaelis.book Page 700 Tuesday, July 29, 2008 3:47 PM

Page 3: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Asynchronous Results Pattern 701

Introducing the Asynchronous Results PatternWith the asynchronous results pattern, you do not code using the Threadclass explicitly. Instead, you use delegate instances. Consider the code inListing 19.1.

Listing 19.1: Asynchronous Results Pattern Example

using System;using System.Threading;

public class AsyncResultPatternIntroduction{

public static AutoResetEvent ResetEvent = new AutoResetEvent(false);

public static void Main() { Console.WriteLine("Application started....");

WorkerThreadHandler workerMethod = null; IAsyncResult asyncResult = null;

try {

Console.WriteLine("Starting thread....");

// Display periods as progress bar.

{ Console.Write('.'); } Console.WriteLine(); Console.WriteLine("Thread ending...."); } finally {

} Console.WriteLine("Application shutting down....");

delegate void WorkerThreadHandler();

workerMethod = new WorkerThreadHandler(DoWork);

asyncResult = workerMethod.BeginInvoke(null, null);

while(!asyncResult.AsyncWaitHandle.WaitOne( 1000, false))

if (workerMethod != null && asyncResult != null) { workerMethod.EndInvoke(asyncResult); }

0321533925_michaelis.book Page 701 Tuesday, July 29, 2008 3:47 PM

Page 4: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Chapter 19: Multithreading Patterns702

}

public static void DoWork() { // TODO: Replace all the pseudocode below // with a real implementation of a long- // running operation Console.WriteLine("\nDoWork() started...."); Thread.Sleep(1000); Console.WriteLine("\nDoWork() ending...."); }}

The results of Listing 19.1 appear in Output 19.1.

Main() begins by instantiating a delegate of type WorkerThreadHandler.As part of the instantiation, the DoWork() method is specified as themethod to execute on a different thread. This line is similar to the instantia-tion of ThreadStart in Chapter 18, except you use your own delegate type,WorkerThreadHandler, rather than one built into the framework. As youshall see shortly, this allows you to pass custom parameters to the thread.

Next, the code calls BeginInvoke(). This method will start the DoWork()method on a thread from the thread pool and then return immediately.This allows you to run other code asynchronously with the DoWork()method. In this example, you print periods while waiting for the DoWork()method to complete.

You monitor the DoWork() method state through a call to IAsyncRe-sult.AsyncWaitHandle.WaitOne() on asyncResult. Like AutoResetE-vent.WaitOne(), IAsyncResult.AsyncWaitHandle.WaitOne() will returnfalse if the timeout (1000 milliseconds) expires before the thread ends. Asa result, the code prints periods to the screen each second during which the

OUTPUT 19.1:

Application started....

Starting thread....

.

DoWork() started....

....................

DoWork() ending....

Thread ending....

Application shutting down....

0321533925_michaelis.book Page 702 Tuesday, July 29, 2008 3:47 PM

Page 5: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Asynchronous Results Pattern 703

DoWork() method is executing. In this example, the mock work in DoWork()is to pause for one second.

Finally, the code calls EndInvoke(). It is important to pass to EndIn-voke() the IAsyncResult reference returned when calling BeginInvoke().IAsyncResult contains the data about the executing worker thread. If thethread identified by the IAsyncResult parameter is still executing, End-Invoke() will block and wait for the DoWork() method to complete. EndIn-voke() does not abort a thread, but blocks the thread until it is done. In thisexample, there is no blocking because you poll the thread’s state in thewhile loop and call EndInvoke() only after the thread has completed.

Passing Data to and from an Alternate ThreadThe introductory example in Listing 19.1 didn’t pass any data to or receiveany data back from the alternate thread. This is rather limiting. Passingdata using fields is an option, but in addition to being cumbersome, such asolution requires the programmer of the called method to explicitly codefor an asynchronous call, rather than just an arbitrary method the callerwants to invoke asynchronously. In other words, the called method mustexplicitly access its required data from the member fields instead of havingthe data passed in via a parameter. Fortunately, the asynchronous resultspattern handles this explicitly.

To begin, you need to change the delegate data type to match the signa-ture of the method you are calling asynchronously. Consider, for example,a method called GetFiles() that returns an array of filenames that match aparticular search pattern. Listing 19.2 shows such a method.

Listing 19.2: Target Method Sample for an Asynchronous Invocation

public static string[] GetFiles( string searchPattern, bool recurseSubdirectories){ string[] results = null;

// Search for files matching the pattern. StringCollection files = new StringCollection(); string directory; directory = Path.GetDirectoryName(searchPattern); if ((directory == null) || (directory.Trim().Length == 0)) { directory = Directory.GetCurrentDirectory();

0321533925_michaelis.book Page 703 Tuesday, July 29, 2008 3:47 PM

Page 6: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Chapter 19: Multithreading Patterns704

}

files.AddRange(GetFiles(searchPattern));

if (recurseSubdirectories) { foreach (string subDirectory in Directory.GetDirectories(directory)) { files.AddRange(GetFiles( Path.Combine( subDirectory, Path.GetFileName(searchPattern)), true)); } }

results = new string[files.Count]; files.CopyTo(results, 0);

return results;}

public static string[] GetFiles(string searchPattern){ string[] fileNames; string directory;

// Set directory , default to the current one if there // is none specified in the searchPattern. directory = Path.GetDirectoryName(searchPattern); if ((directory == null) || (directory.Trim().Length == 0)) { directory = Directory.GetCurrentDirectory(); }

fileNames = Directory.GetFiles( Path.GetFullPath(directory), Path.GetFileName(searchPattern));

return fileNames;}

As input parameters, this method takes the search pattern and a bool indi-cating whether to search subdirectories. It returns an array of strings. Sincethe method could potentially take some time to execute, you decide (per-haps after implementing the method) to call it asynchronously.

0321533925_michaelis.book Page 704 Tuesday, July 29, 2008 3:47 PM

Page 7: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Asynchronous Results Pattern 705

In order to call GetFiles() asynchronously using the asynchronousresults pattern, you need a delegate to match the method signature. Listing19.3 declares such a delegate instead of relying on an existing delegate type.

Listing 19.3: Asynchronous Results with Completed Notification

delegate string[] GetFilesHandler( string searchPattern, bool recurseSubdirectories);

Given the delegate, you can declare a delegate instance and call Begin-Invoke(). Notice that the signature for BeginInvoke() is different from thesignature in the asynchronous results pattern in Listing 19.1. Now thereare four parameters. The last two correspond to the callback and stateparameters. These parameters were in the BeginInvoke() call of Listing19.1 and will be investigated shortly. There are two new parameters at thebeginning, however, and this is not because BeginInvoke() is overloaded.The new parameters are searchPattern and recurseSubdirectories,which correspond to the GetFilesHandler delegate. This enables a call toGetFiles() using the asynchronous results pattern while passing in thedata that GetFiles() needs (see Listing 19.4).

Listing 19.4: Passing Data Back and Forth Using the Asynchronous Results Pattern

using System;using System.IO;using System.Threading;using System.IO;

public class FindFiles{ private static void DisplayHelp() { Console.WriteLine( "FindFiles.exe <search pattern> [/S]\n" + "\n" + "search pattern " + "The directory and pattern to search\n" + " e.g. C:\\Windows\\*.dll\n" + "/s Search subdirectories"); }

delegate string[] GetFilesHandler( string searchPattern, bool recurseSubdirectories);

0321533925_michaelis.book Page 705 Tuesday, July 29, 2008 3:47 PM

Page 8: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Chapter 19: Multithreading Patterns706

public static void Main(string[] args) { string[] files; string searchPattern; bool recurseSubdirectories = false; IAsyncResult result = null;

// Assign searchPattern & recurseSubdirectories switch (args.Length) { case 2: if (args[1].Trim().ToUpper() == "/S") { recurseSubdirectories = true; } goto case 1; case 1: searchPattern = args[0]; // Validate search pattern // ... break; default: DisplayHelp(); return; }

Console.WriteLine("Searching: {0}", searchPattern ); if (recurseSubdirectories) { Console.WriteLine("\trecursive..."); }

// Display periods every second to indicate // the program is running and not frozen. while(!result.AsyncWaitHandle.WaitOne(1000, false)) { Console.Write('.'); } Console.WriteLine("");

// Retrieve the results files = (string[])asyncGetFilesHandler.EndInvoke(result);

// Display the results foreach (string file in files)

GetFilesHandler asyncGetFilesHandler= GetFiles;

result = asyncGetFilesHandler.BeginInvoke( searchPattern, recurseSubdirectories, null, null);

0321533925_michaelis.book Page 706 Tuesday, July 29, 2008 3:47 PM

Page 9: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Asynchronous Results Pattern 707

{ // Display only the filename, not the directory Console.WriteLine(Path.GetFileName(file)); } }

{ string[] results = null;

// ...

return results; }}

The results of Listing 19.4 appear in Output 19.2.

As demonstrated in Listing 19.4, you also need to retrieve the datareturned from GetFiles(). The return from GetFiles() is retrieved in thecall to EndInvoke(). Just as the compiler generated the BeginInvoke()method signature to match all input parameters on the delegate, the End-Invoke() signature matches all the output parameters. The return fromgetFilesMethod.EndInvoke(), therefore, is a string[], matching thereturn of GetFiles(). In addition to the return, any parameters marked asref or out would be part of the EndInvoke() signature as well.

Consider a delegate type that includes out and ref parameters, asshown in Figure 19.1.

An in parameter, such as data, appears only in the BeginInvoke() callbecause only the caller needs to pass such parameters. Similarly, an outparameter, changeDescription, appears only in the EndInvoke() signature.

public static string[] GetFiles( string searchPattern, bool recurseSubdirectories)

OUTPUT 19.2:

Searching: C:\Samples\*.cs

recursive...

AsyncResultPatternIntroduction.cs

FindFilesWithoutNotificationOrState.cs

FindFilesWithNotification.cs

FindFiles.cs

AutoResetEventSample.cs

RunningASeparateThread.cs

0321533925_michaelis.book Page 707 Tuesday, July 29, 2008 3:47 PM

Page 10: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Chapter 19: Multithreading Patterns708

Notice, however, that the ref parameter (value) appears in both BeginIn-voke() and EndInvoke(), since a ref parameter is passed into both the func-tion and a parameter via which data will be returned.

In summary, all delegates created by the C# compiler include theBeginInvoke() and EndInvoke() methods, and these are generated basedon the delegate parameters.

Receiving Notification of Thread CompletionListing 19.1 and Listing 19.4 poll to determine whether the DoWork() orGetFiles() method is running. Since polling is generally not very efficientor convenient, a notification mechanism that fires an event once the threadhas completed is preferable. This is what the AsyncCallback delegate typeis for, and an instance of this delegate is passed as the second-to-lastparameter of BeginInvoke(). Given an AsyncCallback instance, the asyncpattern will invoke the callback delegate once the method has completed.Listing 19.5 provides an example, and Output 19.3 shows the results.

Listing 19.5: Asynchronous Results with Completed Notification

using System;using System.IO;

using System.Threading;using System.IO;

public class FindFilesWithNotifications

Figure 19.1: Delegate Parameter Distribution to BeginInvoke() and EndInvoke()

using System.Runtime.Remoting.Messaging;

System.IAsyncResult UpdateHandler.BeginInvoke( object[] data, ref object value);

delegate bool UpdateHandler( object[] data, ref object value, out changeDescription);

bool UpdateHandler.EndInvoke( ref object value, out changeDescription);

0321533925_michaelis.book Page 708 Tuesday, July 29, 2008 3:47 PM

Page 11: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Asynchronous Results Pattern 709

{ // DisplayHelp() method // ...

delegate string[] GetFilesHandler( string searchPattern, bool recurseSubdirectories);

public static void Main(string[] args) { string searchPattern; bool recurseSubdirectories = false; IAsyncResult result = null;

// Assign searchPattern & recurseSubdirectories // ...

GetFilesHandler asyncGetFilesHandler = GetFiles;

Console.WriteLine("Searching: {0}", args[0]); if (recurseSubdirectories) { Console.WriteLine("\trecursive..."); }

}

public static string[] GetFiles( string searchPattern, bool recurseSubdirectories) { string[] files = null;

// Search for files matching the pattern. // See Listing 19.2. // ...

return files; }

Console.WriteLine("Push ENTER to cancel/exit...");

result = asyncGetFilesHandler.BeginInvoke( args[0], recurseSubdirectories, SearchCompleted, null);

Console.ReadLine();

public static void SearchCompleted(IAsyncResult result) { AsyncResult asyncResult = (AsyncResult)result; GetFilesHandler handler = (GetFilesHandler)asyncResult.AsyncDelegate; string[] files = handler.EndInvoke(result);

0321533925_michaelis.book Page 709 Tuesday, July 29, 2008 3:47 PM

Page 12: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Chapter 19: Multithreading Patterns710

}

Callback notification when the worker thread completes provides a keybenefit of using the asynchronous results pattern over manual threadmanipulation. For example, it allows developers to display a widget toindicate that a task has completed.

As already demonstrated, EndInvoke() can be called from within Main()using the delegate instance and the IAsyncResult reference returned fromBeginInvoke(). However, EndInvoke() will block until the asynchronous callcompletes. As a result, it is preferable to call EndInvoke() from within the call-back. To accomplish this, the callback function casts its IAsyncResult param-eter, but the cast is unintuitive. The data type is AsyncResult, but thenamespace is the unintuitive System.Runtime.Remoting.Messaging. Search-Completed() demonstrates the code for calling EndInvoke() from within thethread completion callback. Listing 19.6 shows the fully qualified call.

Listing 19.6: Calling EndInvoke() from within Asynchronous Callback

...public static void SearchCompleted(IAsyncResult result){

GetFilesHandler handler = (GetFilesHandler)asyncResult.AsyncDelegate; string[] files = handler.EndInvoke(result); ...}

foreach (string file in files) { Console.WriteLine(Path.GetFileName(file)); } }

OUTPUT 19.3:

Searching: C:\Samples\*.cs

recursive...

Push ENTER to cancel/exit...

AsyncResultPatternIntroduction.cs

FindFilesWithoutNotificationOrState.cs

FindFilesWithNotification.cs

FindFiles.cs

AutoResetEventSample.cs

RunningASeparateThread.cs

System.Runtime.Remoting.Messaging.AsyncResult asyncResult =(System.Runtime.Remoting.Messaging.AsyncResult)result;

0321533925_michaelis.book Page 710 Tuesday, July 29, 2008 3:47 PM

Page 13: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Asynchronous Results Pattern 711

Passing Arbitrary StateThe last parameter in the BeginInvoke() call is of type object, and pro-vides a mechanism for passing arbitrary data to the callback method(SearchCompleted()). Consider a situation in which multiple threads werestarted one after the other, and in each case the callback was to the sameAsyncCallback delegate instance. The problem is that from within theasync callback delegate you don’t correlate which completed call to Get-Files() corresponds to which call that initiated the GetFiles() method.For example, you cannot print out which search results correspond towhich searchPattern.

Fortunately, this is the purpose of the last parameter in BeginInvoke().Listing 19.7 starts the GetFiles() method for each search pattern passed inthe command line. In each call, you pass the search pattern (arg) twice toasyncGetFilesHandler.BeginInvoke(): once as a parameter to the Get-Files() method that is to run asynchronously, and once as the last param-eter to be accessed from inside SearchCompleted().

Listing 19.7: Asynchronous Results with Completed Notification

using System;using System.IO;using System.Runtime.Remoting.Messaging;using System.IO;

public class FindFiles{ delegate string[] GetFilesHandler( string searchPattern, bool recurseSubdirectories);

public static void Main(string[] args) { bool recurseSubdirectories = true; IAsyncResult[] result = new IAsyncResult[args.Length]; int count = 0;

foreach(string arg in args) { if (arg.Trim().ToUpper() == "/S") { recurseSubdirectories = true; break; } }

0321533925_michaelis.book Page 711 Tuesday, July 29, 2008 3:47 PM

Page 14: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Chapter 19: Multithreading Patterns712

GetFilesHandler asyncGetFilesHandler = GetFiles;

Console.WriteLine("Searching: {0}", string.Join(", ", args)); if (recurseSubdirectories) { Console.WriteLine("\trecursive..."); } Console.WriteLine("Push ENTER to cancel/exit...");

{ if (arg.Trim().ToUpper() != "/S") { result[count] = asyncGetFilesHandler.BeginInvoke( arg, recurseSubdirectories, SearchCompleted, arg); } count++; } Console.ReadLine(); }

public static string[] GetFiles( string searchPattern, bool recurseSubdirectories) { string[] files;

// Search for files matching the pattern. // See Listing 19.2. // ...

return files; }

public static void SearchCompleted(IAsyncResult result) {

AsyncResult asyncResult = (AsyncResult)result; GetFilesHandler handler = (GetFilesHandler)asyncResult.AsyncDelegate; string[] files = handler.EndInvoke(result); foreach (string file in files) { Console.WriteLine("\t"+ Path.GetFileName(file)); } }}

foreach (string arg in args)

string searchPattern = (string)result.AsyncState; Console.WriteLine("{0}:", searchPattern);

0321533925_michaelis.book Page 712 Tuesday, July 29, 2008 3:47 PM

Page 15: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Asynchronous Results Pattern 713

The results of Listing 19.7 appear in Output 19.4.

Since the listing passes arg (the search pattern) in the call to BeginIn-voke(), it can retrieve the search pattern from the IAsyncResult parameterof SearchCompleted(). To do this, it accesses the IAsyncResult.AsyncStateproperty. Because it is of type object, the string data type passed duringBeginInvoke() needs to be downcast to string. In this way, it can displaythe search pattern above the list of files that meet the search pattern.

Asynchronous Results ConclusionsOne of the key features the asynchronous results pattern offers is that thecaller determines whether to call a method asynchronously. The calledobject may choose to provide asynchronous methods explicitly, perhapseven using the asynchronous results pattern internally. However, this isnot a requirement for the caller to make an asynchronous call.

Called methods may choose to provide asynchronous APIs explicitlywhen the called class can implement the asynchronous functionality moreefficiently than the caller can, or when it is determined that asynchronousmethod calls are likely, such as with methods that are relatively slow. If itis determined that a method should explicitly provide an asynchronouscalling pattern, it is a good practice for the API to follow the asynchronousresults design pattern. An explicit implementation, therefore, shouldinclude methods that correspond to BeginInvoke(), along with eventscallers can subscribe to in order to be notified when a thread completes.

For example, starting in .NET 2.0, the System.Net.WebClient classincludes asynchronous method calls for downloading files. To begin the

OUTPUT 19.4:

Searching: C:\Samples\*.cs, C:\Samples\*.exe

recursive...

Push ENTER to cancel/exit...

C:\Samples\*.cs

AsyncResultPatternIntroduction.cs

FindFilesWithoutNotificationOrState.cs

FindFilesWithNotification.cs

FindFiles.cs

AutoResetEventSample.cs

C:\Samples\*.exe

FindFiles.exe

0321533925_michaelis.book Page 713 Tuesday, July 29, 2008 3:47 PM

Page 16: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Chapter 19: Multithreading Patterns714

download, it includes the DownloadFileAsync() method. Additionally, thecaller can register for notification when the download is complete usingthe DownloadFileCompleted event.

The .NET 2.0 (and above) implementation of System.Net.WebClientalso includes a DownloadProgressChanged event for publishing when adownload operation successfully transfers some of its data, as well as aCancelAsync() method to discontinue the download. These are not explic-itly part of the asynchronous results pattern, and therefore, they are notavailable unless provided by the called class explicitly. These methods areanother reason why programmers may explicitly implement asynchro-nous APIs, instead of just relying on the caller to use the asynchronousresults pattern. To help with such methods, .NET 2.0 (and above) includesthe System.ComponentModel.BackgroundWorker class.

Although not supplied by System.Net.WebClient, a method corre-sponding to EndInvoke() is also a nice addition for explicitly implementedasynchronous calls.

The asynchronous results pattern relies on the built-in thread pool thatprovides support for reusing threads rather than always creating newones. The pool has a number of threads, dependent on the number of pro-cessors, and the thread pool will wait for a free thread before servicing therequest. If a request for a new thread is needed but the creation of a newthread would undermine the value of multithreading (because the cost ofan additional thread would outweigh its benefit), the thread pool won’tallocate a thread until another thread is released back to the thread pool.

Background Worker PatternFrequently with multithreaded operations, not only do you want to benotified when the thread completes, you want the method to provide anupdate on the status of the operation. Often, users want to be able to cancellong-running tasks. The .NET Framework 2.0 (and above) includes a Back-groundWorker class for programming this type of pattern.

Listing 19.8 is an example of this pattern. It calculates pi to the numberof digits specified.

0321533925_michaelis.book Page 714 Tuesday, July 29, 2008 3:47 PM

Page 17: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Background Worker Pattern 715

Listing 19.8: Using the Background Worker Pattern

using System;using System.Threading;using System.ComponentModel;using System.Text;

public class PiCalculator{

public static AutoResetEvent resetEvent = new AutoResetEvent(false);

public static void Main() { int digitCount;

Console.Write( "Enter the number of digits to calculate:"); if (int.TryParse(Console.ReadLine(), out digitCount)) { Console.WriteLine("ENTER to cancel");

resetEvent.WaitOne(); } else { Console.WriteLine( "The value entered is an invalid integer."); } }

public static BackgroundWorker calculationWorker = new BackgroundWorker();

// C# 2.0 Syntax for registering delegates calculationWorker.DoWork += CalculatePi; // Register the ProgressChanged callback calculationWorker.ProgressChanged += UpdateDisplayWithMoreDigits; calculationWorker.WorkerReportsProgress = true; // Register a callback for when the // calculation completes calculationWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Complete); calculationWorker.WorkerSupportsCancellation = true;

// Begin calculating pi for up to digitCount digits calculationWorker.RunWorkerAsync(digitCount);

Console.ReadLine(); // If cancel is called after the calculation // has completed it doesn't matter. calculationWorker.CancelAsync(); // Wait for Complete() to run.

0321533925_michaelis.book Page 715 Tuesday, July 29, 2008 3:47 PM

Page 18: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Chapter 19: Multithreading Patterns716

private static void CalculatePi( object sender, DoWorkEventArgs eventArgs) { int digits = (int)eventArgs.Argument;

StringBuilder pi = new StringBuilder("3.", digits + 2); calculationWorker.ReportProgress(0, pi.ToString());

// Calculate rest of pi, if required if (digits > 0) { for (int i = 0; i < digits; i += 9) {

// Calculate next i decimal places int nextDigit = PiDigitCalculator.StartingAt( i + 1); int digitCount = Math.Min(digits - i, 9); string ds = string.Format("{0:D9}", nextDigit); pi.Append(ds.Substring(0, digitCount));

// Show current progress calculationWorker.ReportProgress( 0, ds.Substring(0, digitCount));

// Check for cancellation if (calculationWorker.CancellationPending) { // Need to set Cancel if you need to // distinguish how a worker thread completed // i.e., by checking // RunWorkerCompletedEventArgs.Cancelled eventArgs.Cancel = true; break; } } }

eventArgs.Result = pi.ToString(); }

private static void UpdateDisplayWithMoreDigits( object sender, ProgressChangedEventArgs eventArgs) { string digits = (string)eventArgs.UserState;

Console.Write(digits); }

0321533925_michaelis.book Page 716 Tuesday, July 29, 2008 3:47 PM

Page 19: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Background Worker Pattern 717

static void Complete( object sender, RunWorkerCompletedEventArgs eventArgs) { // ... }}

public class PiDigitCalculator{ // ...}

Establishing the PatternThe process of hooking up the background worker pattern is as follows.

1. Register the long-running method with the Background-Worker.DoWork event. In this example, the long-running task is the call to CalculatePi().

2. To receive progress or status notifications, hook up a listener to Back-groundWorker.ProgressChanged and set BackgroundWorker.Worker-ReportsProgress to true. In Listing 19.8, the UpdateDisplayWithMoreDigits() method takes care of updating the display as more digits become available.

3. Register a method (Complete()) with the BackgroundWorker.Run-WorkerCompleted event.

4. Assign the WorkerSupportsCancellation property to support can-cellation. Once this property is assigned the value true, a call to BackgroundWorker.CancelAsync will set the DoWorkEventArgs.Can-cellationPending flag.

5. Within the DoWork-provided method (CalculatePi()), check the DoWorkEventArgs.CancellationPending property and exit the method when it is true.

6. Once everything is set up, you can start the work by calling Back-groundWorker.RunWorkerAsync() and providing a state parameter that is passed to the specified DoWork() method.

0321533925_michaelis.book Page 717 Tuesday, July 29, 2008 3:47 PM

Page 20: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Chapter 19: Multithreading Patterns718

When you break it down into steps, the background worker pattern isrelatively easy to follow and provides the advantage over the asynchro-nous results pattern of a mechanism for cancellation and progress notifica-tion. The drawback is that you cannot use it arbitrarily on any method.Instead, the DoWork() method has to conform to a System.Component-Model.DoWorkEventHandler delegate, which takes arguments of typeobject and DoWorkEventArgs. If this isn’t the case, a wrapper function isrequired. The cancellation- and progress-related methods also require spe-cific signatures, but these are in control of the programmer setting up thebackground worker pattern.

Exception HandlingIf an unhandled exception occurs while the background worker thread isexecuting, the RunWorkerCompletedEventArgs parameter of the RunWorker-Completed delegate (Completed’s eventArgs) will have an Error property setwith the exception. As a result, checking the Error property within the Run-WorkerCompleted callback in Listing 19.9 provides a means of handling theexception.

Listing 19.9: Handling Unhandled Exceptions from the Worker Thread

// ... static void Complete( object sender, RunWorkerCompletedEventArgs eventArgs) { Console.WriteLine(); if (eventArgs.Cancelled) { Console.WriteLine("Cancelled"); }

else { Console.WriteLine("Finished"); } resetEvent.Set(); } // ...

else if (eventArgs.Error != null) { // IMPORTANT: check error to retrieve any exceptions. Console.WriteLine( "ERROR: {0}", eventArgs.Error.Message); }

0321533925_michaelis.book Page 718 Tuesday, July 29, 2008 3:47 PM

Page 21: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Windows Forms 719

It is important that the code check eventArgs.Error inside the RunWorker-Completed callback. Otherwise, the exception will go undetected; it won’teven be reported to AppDomain.

Windows FormsOne more important threading concept relates to user interface develop-ment using the System.Windows.Forms namespace. The Microsoft Win-dows suite of operating systems uses a single-threaded, message-processing-based user interface. This means that only one thread at a timeshould access the user interface, and any alternate thread interactionshould be marshaled via the Windows message pump. The processinvolves calling a component’s InvokeRequired property to determinewhether marshaling is necessary. Internally, Invoke() will check Invok-eRequired anyway, but it can be more efficient to do so beforehand explic-itly. Listing 19.10 demonstrates this pattern.

Listing 19.10: Accessing the User Interface via Invoke()

using System;using System.Drawing;using System.Threading;using System.Windows.Forms;

class Program : Form{ private System.Windows.Forms.ProgressBar _ProgressBar;

[STAThread] static void Main() { Application.Run(new Program()); }

public Program() { InitializeComponent(); ThreadStart threadStart = Increment; threadStart.BeginInvoke(null, null); }

void UpdateProgressBar() {

0321533925_michaelis.book Page 719 Tuesday, July 29, 2008 3:47 PM

Page 22: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Chapter 19: Multithreading Patterns720

}

private void Increment() { for (int i = 0; i < 100; i++) { UpdateProgressBar(); Thread.Sleep(100); }

}

private void InitializeComponent() { _ProgressBar = new ProgressBar(); SuspendLayout();

_ProgressBar.Location = new Point(13, 17); _ProgressBar.Size = new Size(267, 19);

ClientSize = new Size(292, 53); Controls.Add(this._ProgressBar); Text = "Multithreading in Windows Forms"; ResumeLayout(false); }}

This program displays a window that contains a progress bar that auto-matically starts incrementing. Once the progress bar reaches 100 percent,the dialog box closes.

if (_ProgressBar.InvokeRequired) { MethodInvoker updateProgressBar = UpdateProgressBar; _ProgressBar.Invoke(updateProgressBar); } else { _ProgressBar.Increment(1); }

if (InvokeRequired) { // Close cannot be called directly from // a non-UI thread. Invoke(new MethodInvoker(Close)); } else { Close(); }

0321533925_michaelis.book Page 720 Tuesday, July 29, 2008 3:47 PM

Page 23: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Windows Forms 721

Notice from Listing 19.10 that you have to check InvokeRequired twice,and then the marshal calls across to the user interface thread if it returnstrue. In both cases, the marshaling involves instantiating a MethodInvokerdelegate that is then passed to Invoke(). Since marshaling across toanother thread could be relatively slow, an asynchronous invocation of thecall is also available via BeginInvoke() and EndInvoke().

Invoke(), BeginInvoke(), EndInvoke(), and InvokeRequired comprisethe members of the System.ComponentModel.ISynchronizeInvoke inter-face which is implemented by System.Windows.Forms.Control, fromwhich Windows Forms controls derive.

A D V A N C E D T O P I C

Controlling the COM Threading Model with the STAThreadAttributeWith COM, four different apartment-threading models determine thethreading rules relating to calls between COM objects. Fortunately, theserules—and the complexity that accompanied them—have disappearedfrom .NET as long as the program invokes no COM components. The gen-eral approach to handling COM Interop is to place all .NET componentswithin the main, single-threaded apartment by decorating a process’s Mainmethod with the System.STAThreadAttribute. In so doing, it is not neces-sary to cross apartment boundaries to invoke the majority of COM compo-nents. Furthermore, apartment initialization does not occur, unless a COMInterop call is made.

COM Interop is not necessarily an explicit action by the developer.Microsoft implemented many of the components within the .NET Frame-work by creating a runtime callable wrapper (RCW) rather than rewritingall the COM functionality within managed code. As a result, COM calls areoften made unknowingly. To ensure that these calls are always made froma single-threaded apartment, it is generally a good practice to decorate themain method of all Windows Forms executables with the System.STATh-readAttribute.

0321533925_michaelis.book Page 721 Tuesday, July 29, 2008 3:47 PM

Page 24: Multithreading Patterns - IntelliTect · 2015-11-11 · 699 19 Multithreading Patterns HAPTER 18 FOCUSED on managing threads and synchronizing the data the threads share. As developers

Chapter 19: Multithreading Patterns722

SUMMARY

This chapter used a step-by-step approach to setting up both the asynchro-nous results pattern and the background worker pattern. The asynchro-nous results pattern provides support for calling any methodasynchronously, even a method written by a third party. It includes a notifi-cation mechanism about method execution completion via a callback on adelegate passed when setting up the pattern. One drawback to the asyn-chronous results pattern is that there is no inherent mechanism for postingthe status of the asynchronous method. However, the .NET Framework 2.0(and higher) provides this functionality in a second multithreading patterncalled the background worker pattern. The key about this pattern is that itincludes support for notification of status (without polling), completion,and errors. To support this, however, the pattern requires special codehooks within the asynchronously invoked method. This prevents develop-ers from using it on methods for which they have no source code, or if theyare unwilling to code special hooks. Calling long-running methods pro-vided by third parties, for example, prevents the support for embeddingthe callback hooks within the methods.

The next chapter investigates another fairly complex .NET technology:that of marshaling calls out of .NET and into managed code using P/Invoke. In addition, it introduces a concept known as unsafe code, which isused to access memory pointers directly, as in C++.

0321533925_michaelis.book Page 722 Tuesday, July 29, 2008 3:47 PM


Recommended