In the article on The dangers of ThreadLocal I explained how the introduction of async/await forces us to unlearn what we perceived to be true in the “old world” when Threads dominated our software lingo. We’re now at a point where we need to re-evaluate how we approach thread safety in our codebases while using the async/await constructs. In today’s post-thread era, we should strive to remove all thread (task-local) state and let the state float into the code which needs it. Let’s take a look at how we can implement the idea of floating state and make our code robust and ready for concurrency.

Identifying the context

Consider the following example where we have a WorkerOrchestrator which has the responsibility to orchestrate multiple asynchronous workers.

class WorkerOrchestrator { public Task DoWork(int concurrency) { var worker = new Worker(); var workTasks = new Task[concurrency]; for (int i = 0; i < concurrency; i++) { workTasks[i] = worker.Work(); } return Task.WhenAll(workTasks); } }

The orchestrator creates a Worker instance and calls the Work method multiple times based on the provided concurrency number. Instead of awaiting each worker’s task to be finished, it adds each of the tasks to an array. Note that the code does not await the worker.Work() invocation as that would cause the work to be done by the workers one-by-one instead of running them concurrently. At the end of the DoWork method the worker tasks are passed into Task.WhenAll , which will only complete when all of the tasks in the collection have been completed. Let’s have a look at the Worker implementation.

class Worker { static ThreadLocal<Validator> validator = new ThreadLocal<Validator>(() => new Validator()); public async Task Work() { await Step1(); await Step2(); } Task Step1() { return validator.Value.Validate(new Step1Context()); } Task Step2() { return validator.Value.Validate(new Step2Context()); } }

When data inside classes, such as fields or properties, is allocated it exists within the context of the instance of the class that owns it. Sometimes it is desirable to store context-related data that automatically follows the flow of execution between execution scopes. Such data is no longer owned by an instance of a class. Instead it is owned by the execution flow.

The pattern that implements execution-scoped data is called Ambient Context. We can find many examples of Ambient Context in the .NET Framework. When you are using TransactionScope, the current transaction that it sets can be found in Transaction.Current . In web applications HttpContext.Current represents the data of the currently handled HTTP call.

In the example above, the Worker class has a ThreadLocal variable internally which creates a new instance of Validator for each Thread accessing it. The Worker implementation uses the current threading ambient context as a ThreadLocal . Each call to the worker’s Work method issued by a thread will create a new instance of the Validator . All calls happening on the same thread can access the same instance of a Validator .

Code calling the worker’s Work method can’t possibly know which context the Validator instance exists in since the validator isn’t part of the public contract on the Worker class. Because of this, it is not possible to have the Validator instance run with the same context as the calling code. This will result in the possibility of multiple instances of a Validator exiting inside the call stack of a Work method. Remember, each await statement is an opportunity for the calling thread to work on other tasks and there is no guarantee that the same thread will execute the code after the await statement.

Knowing that this may happen, we need to figure out how we can get rid of the ambient context without breaking the thread-safety of our code.

Floating the state

The best possible way is to declare a dependency to a Validator instance at the method level (also called method-injection). Let’s see how this change would look when applied to the Worker class:

class Worker { public async Task Work(Validator validator) { await Step1(validator); await Step2(validator); } Task Step1(Validator validator) { return validator.Validate(new Step1Context()); } Task Step2(Validator validator) { return validator.Validate(new Step2Context()); } }

As you can see, the methods were modified to accept a Validator instance. This design change “ripples” from the internal methods, Step1 and Step2 , up to the Work method on the public interface of the worker. Let’s see how this impacts the WorkerOrchestrator :

public class WorkerOrchestrator { public Task DoWork(int concurrency) { ... for (i = 0; i < concurrency; i++) { workTasks[i] = worker.Work(new Validator()); } ... } }

The implication of this change is that the caller of the worker is now required to pass in an instance of a Validator every time it calls the Work method.

Drifting away