Timers

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 20, 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.

Listing 22.13: Using Task.Delay() as a Timer
using System;
using System.Threading.Tasks;
 
public class Pomodoro
{
    // ...
 
    private static async Task TickAsync(
        System.Threading.CancellationToken token)
    {
        for(int minute = 0; minute < 25; minute++)
        {
            DisplayMinuteTicker(minute);
            for(int second = 0; second < 60; second++)
            {
                await Task.Delay(1000, token);
                if(token.IsCancellationRequested)
                    break;
                DisplaySecondTicker();
            }
            if(token.IsCancellationRequested)
                break;
        }
    }
    // ...
}

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.

AdVanced/Beginner Topic
Controlling the COM Threading Model with the STAThreadAttribute

With COM, four different apartment-threading models determine the threading rules relating to calls between COM objects. Fortunately, these rules—and the complexity that accompanied them—have disappeared from .NET as long as the program invokes no COM components. The general approach to handling COM interoperability issues is to place all .NET components within the main, single-threaded apartment by decorating a process’s Main method with the System.STAThreadAttribute. This approach means it is not necessary to cross apartment boundaries to invoke the majority of COM components. Furthermore, apartment initialization does not occur unless a COM interop call is made. The caveat to this approach is that all other threads (including those of Task) will default to using a multithreaded apartment (MTA). In turn, care needs to be taken when invoking COM components from other threads besides the main one.

COM interop is not necessarily an explicit action by the developer. Microsoft implemented many of the components within the Microsoft .NET Framework by creating a runtime callable wrapper (RCW) rather than rewriting all of the COM functionality within managed code. As a result, COM calls are often made unknowingly. To ensure that these calls are always made from a single-threaded apartment, it is generally a good practice to decorate the main method of all Windows Forms executables with the System.STAThreadAttribute.

Chapter 22: Summary

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++).

________________________________________

5. Starting in C# 5.0.
6. In C# 5.0.
{{ snackbarMessage }}
;