Synchronization to Avoid Thread Safety Issues
To implement an asynchronous callback, I’ve restructured the code. Listing 3 illustrates the Main() function.
Listing 3 The Main() function in the asynchronous callback example.
static void Main(string[] args) { Console.WriteLine("Asynchronous Operation with Callback"); Console.WriteLine("Main() thread {0}.", Thread.CurrentThread.GetHashCode()); NetworkOperation netOp = new NetworkOperation(UploadFirmware); IAsyncResult iftAR = netOp.BeginInvoke( new AsyncCallback(OperationComplete), "After network operation: Now back in Main()."); Console.WriteLine("Upload is running - do more work now!"); for(int i = 1; i <= 5; i++) { Console.WriteLine("Parallel work in Main() - Item {0} of {1}", i, 5); Thread.Sleep(1000); } Console.ReadLine(); }
The key element of note in Listing 3 is the class IAsyncResult. This class allows us to invoke the upload code and be informed of the completion in an asynchronous fashion. This goal is achieved with the following line:
netOp.BeginInvoke(new AsyncCallback(OperationComplete), "After network operation: Now back in Main().");
The method OperationComplete() is called when the upload operation is complete. The calling code doesn’t have to wait or do any expensive polling—we’re automatically informed upon completion of the operation. Listing 4 shows the modified upload code.
Listing 4 The modified upload code.
static int UploadFirmware() { const int interBlockDelay = 1500; int numberOfBlocks = 5; Console.WriteLine("UploadFirmware() thread {0}.", Thread.CurrentThread.GetHashCode()); Console.WriteLine("Thread \"{0}\" is executing UploadFirmware()", Thread.CurrentThread.Name); // Do the content download. Console.WriteLine("Now executing the upload block transfers"); for(int i = 1; i <= numberOfBlocks; i++) { Console.WriteLine("Uploading on Thread {0} Block {1} of {2}", Thread.CurrentThread.GetHashCode(), i, numberOfBlocks); Thread.Sleep(interBlockDelay); } return numberOfBlocks; }
The method in Listing 4 is referenced by the code in Listing 3. To tie it all together, we have the code in Listing 5, which is called when the network operation completes.
Listing 5 The operation completion code.
static void OperationComplete(IAsyncResult itfAR) { Console.WriteLine("OperationComplete() thread {0}.", Thread.CurrentThread.GetHashCode()); Console.WriteLine("Firmware upload is complete"); // Retrieve the operation result. AsyncResult ar = (AsyncResult)itfAR; NetworkOperation netOp = (NetworkOperation)ar.AsyncDelegate; Console.WriteLine("Blocks uploaded: {0}.", netOp.EndInvoke(itfAR)); // Retrieve the informational object and cast it to string string msg = (string)itfAR.AsyncState; Console.WriteLine(msg); }
I know this all seems very complicated, but we basically have four steps:
- Prepare the network operation code (the upload code).
- Prepare the asynchronous operation code.
- Start the network operation.
- Do work in parallel with the asynchronous operation code.
Figure 3 shows the code during execution.
Figure 3 The asynchronous code in action.
Three main points to note in Figure 3:
- The lines with Parallel work occur in the Main() method—during thread execution.
- The lines with Uploading on... occur in the threaded code.
- The upload code is complete when the line with OperationComplete() is displayed.
As Figure 3 shows, the user is free to do work in the Main() method in parallel with the upload code. When the upload code is finished, the Main() method is informed via the automatically invoked OperationComplete().