Books & ebooks about Microsoft tools, technologies, & research. Plus programming best practices. We hope you enjoy this post.
Good morning everyone, Jeffrey Richter here. Today I thought I’d share a section of my new book, CLR via C#, Third Edition, with you. It’s from Chapter 26, “Compute-Bound Asynchronous Operations.” The section discusses how to flow contextual information from one thread to another thread by using the CLR’s Execution Context infrastructure. During the discussion, I go into the security and performance details of this feature as well.
Execution ContextsEvery thread has an execution context data structure associated with it. The executioncontext includes things such as security settings (compressed stack, Thread’sPrincipal property, and Windows identity), host settings (see System.Threading.HostExecutionContextManager), and logical call context data (see System.Runtime.Remoting.Messaging.CallContext’s LogicalSetData and LogicalGetData methods).When a thread executes code, some operations are affected by the values of the thread’sexecution context settings. This is certainly true for the security settings. Ideally, whenever athread uses another (helper) thread to perform tasks, the issuing thread’s execution contextshould flow (be copied) to the helper thread. This ensures that any operations performedby helper thread(s) are executing with the same security settings and host settings. It alsoensures that any data stored in the initiating thread’s logical call context is available to thehelper thread.
By default, the CLR automatically causes the initiating thread’s execution context to flow toany helper threads. This transfers context information to the helper thread, but it comes at aperformance cost because there is a lot of information in an execution context, and accumulatingall of this information and then copying it for the helper thread takes a fair amount oftime. If the helper thread then employs additional helper threads, then more execution contextdata structures have to be created and initialized as well.
In the System.Threading namespace, there is an ExecutionContext class that allows you tocontrol how a thread’s execution context flows from one thread to another. Here is what theclass looks like:
public sealed class ExecutionContext : IDisposable, ISerializable { [SecurityCritical] public static AsyncFlowControl SuppressFlow(); public static void RestoreFlow(); public static Boolean IsFlowSuppressed();
// Less commonly used methods are not shown}
You can use this class to suppress the flowing of an execution context, thereby improvingyour application’s performance. The performance gains can be quite substantial for aserver application. There is not much performance benefit for a client application, and theSuppressFlow method is marked with the [SecurityCritical] attribute, making it impossibleto call in some client applications (like Silverlight). Of course, you should suppress theflowing of execution context only if the helper thread does not need or access the contextinformation. If the initiating thread’s execution context does not flow to a helper thread, thehelper thread will use whatever execution context it last associated with it. Therefore, thehelper thread really shouldn’t execute any code that relies on the execution context state(such as a user’s Windows identity).
Here is an example showing how suppressing the flow of execution context affects data in athread’s logical call context when queueing a work item to the CLR’s thread pool1:
public static void Main() { // Put some data into the Main thread's logical call context CallContext.LogicalSetData("Name", "Jeffrey");
// Initiate some work to be done by a thread pool thread // The thread pool thread can access the logical call context data ThreadPool.QueueUserWorkItem( state => Console.WriteLine("Name={0}", CallContext.LogicalGetData("Name")));
// Now, suppress the flowing of the Main thread's execution context ExecutionContext.SuppressFlow();
// Initiate some work to be done by a thread pool thread // The thread pool thread can NOT access the logical call context data ThreadPool.QueueUserWorkItem( state => Console.WriteLine("Name={0}", CallContext.LogicalGetData("Name")));
// Restore the flowing of the Main thread's execution context in case // it employs more thread pool threads in the future ExecutionContext.RestoreFlow(); ...}
When I compile and run the code above, I get the following output:
Name=JeffreyName=
While this discussion has focused on suppressing the flow of execution context when callingThreadPool.QueueUserWorkItem, this technique is also useful when using Task objects (discussedin the “Tasks” section of this chapter) and is also useful when initiating asynchronousI/O operations (discussed in Chapter 27, “I/O-Bound Asynchronous Operations”).
Devon here. Jeffrey’s book will be available via online retailers around February 15. The book’s ISBN is 978-0735627048, and it contains 704 pages.