Puppet Task – TaskCompletionSource

Occasionally we will either want to wrap some code in a Task to allow us to use async await and maybe creating an actual Task is not required. It could be legacy code, for example maybe we’re running a threadpool thread and want to make this appear as a async await capable method or maybe we are mocking a method in our unit tests.

In such cases we can use the TaskCompletionSource.

Let’s look at a slightly convoluted example. We have a third party (or binary only) library with a ExternalMethod class and a method called CalculateMeanAsync which uses the threadpool to execute some process and when the process is completed it calls the supplied callback Action passing a double value as a result. So it’s method signature might look like

public static void CalculateMeanValueAsync(Action<double> callback)

Now we want to use this method in a more async await style i.e. we’d like to call the method like this

double d = await CalculateMeanValueAsync();

We want to basically create a task to handle this but we want to manually control it from outside of the external method. We can thus use the TaskCompletedSource as a puppet task, writing something like the code below to wrap the external method call in an async await compatible piece of code.

public Task<double> CalculateMeanValueAsync()
{
   var tcs = new TaskCompletionSource<double>();

   ExternalMethod.CalculateMeanValueAsync(result => 
   {
      tcs.SetResult(result);
   });

   return tcs.Task;
}

What happens here is we create a TaskCompletionSource, and return a Task from it (which obviously may or may not return prior to the ExternalMethod.CalculateMeanValueAsync completion. Our calling code awaits our new CalculateMeanValueAsync method and when the callback Action is called we set the result using tcs.SetResult. This will now cause our TaskCompletionSource task to complete allows our code to continue to any code after the await in the calling method.

So we’ve essentially made a method appear as a Task style async method but controlling the flow via the TaskCompletionSource.

Another example might be in unit testing. When mocking an interface which returns a Task we could create a TaskCompletionSource and create a setup that returns it’s Task property, then set the mock result on the TaskCompletionSource. An example of such a test is listed below:

[Fact]
public async Task CalculateMean_ExpectCallToIStatisticCalculator_ResultShouldBeSuppliedByMock()
{
   TaskCompletionSource<double> tc = new TaskCompletionSource<double>();

   var mock = new Mock<IStatisticsCalculator>();
   mock.Setup(s => s.CalculateMeanAsync()).Returns(tc.Task);

   tc.SetResult(123.45);

   Calculator c = new Calculator(mock.Object);
   double mean = await c.CalculateMeanAsync();
   Assert.Equal(123.45, mean);

   mock.Verify();
}

So in this example we have a Calculator class which takes an IStatisticsCalculator (maybe it allows us to swap in and out different code for the calculations – I didn’t say it was a perfect example). Now in our test we want to create a mock (I’m using xUnit and Moq for this). We expect the Calculator to call the mock code and return the result from it.

As you can see the mock sets up the Task and then we set the result on the TaskCompletionSource to emulate a completion of the task.

Note: In this example you must return Task on your async test method or you may find the test passing even when it clearly shouldn’t