On occasion, it is necessary to delay code execution for a specific period of time or to register for a notification after a specific period of time. Examples include refreshing the screen at specific time intervals, rather than immediately, when frequent data changes occur. One approach to implementing timers is to leverage the async/await pattern5 and the Task.Delay() method added in .NET 4.5. As we pointed out in Chapter 19, one key feature of TAP is that the code executing after an async call will continue in a supported thread context, thereby avoiding any UI cross-threading issues. Listing 22.13 provides an example of how to use the Task.Delay() method.
The call to Task.Delay(1000) will set a countdown timer that triggers after 1 second and executes the continuation code that appears after it.
Fortunately, TAP’s use of the synchronization context6 specifically addressed executing UI-related code exclusively on the UI thread. Prior to that, it was necessary to use specific timer classes that were UI-thread safe—or could be configured as such. Timers such as System.Windows.Forms.Timer, System.Windows.Threading.DispatcherTimer, and System.Timers.Timer (if configured appropriately) are UI-thread friendly. Others, such as System.Threading.Timer, are optimized for performance.
In this chapter, we looked at various synchronization mechanisms and saw how a variety of classes are available to protect against race conditions. Coverage included the lock keyword, which leverages System.Threading.Monitor under the covers. Other synchronization classes include System.Threading.Interlocked, System.Threading.Mutext, System.Threading.WaitHandle, reset events, semaphores, and the concurrent collection classes.
In spite of all the progress made in improving multithreaded programming between early versions of .NET and today, synchronization of multithreaded programming remains complicated, with numerous pitfalls awaiting the unwary developer. To avoid these sand traps, several best practices have been identified, including consistently acquiring synchronization targets in the same order and wrapping static members with synchronization logic.
Before closing the chapter, we considered the Task.Delay() method, a .NET 4.5–introduced API for implementing a timer based on TAP.
The next chapter investigates another complex .NET technology: that of marshaling calls out of .NET and into unmanaged code using P/Invoke. In addition, it introduces a concept known as unsafe code, which C# uses to access memory pointers directly, as unmanaged code does (e.g., C++).