ContinueWith slowly

Previously I discussed the hidden costs of ‘async’ and even discussed a benchmark within the comments. Here is another interesting benchmark on a case that I’ve seen come up a few times. Which of these do you think is faster?

// Option A: use 'async' + 'await'
private static async Task<long> GetNumberAAsync()
    long value = await GetNumberAsync();
    return value + 1;

// Option B: use 'ContinueWith'
private static Task<long> GetNumberBAsync()
    return GetNumberAsync().ContinueWith(t => t.Result + 1, TaskContinuationOptions.ExecuteSynchronously);

// (Inner function to do some trivial async work)
private static async Task<long> GetNumberAsync()
    await Task.Yield();
    return Stopwatch.GetTimestamp();

If you had to take a wild guess, maybe you’d say option B because it doesn’t have the “baggage” of the internal async state machine. But when I measured, I got these results:

Await (A) vs. ContinueWith (B)

Await (A) vs. ContinueWith (B)

This is 100 data points for each benchmark arranged in sorted order. The vertical axis is ticks per iteration, so higher is worse. Surprisingly, the data from my experiment showed that option A (‘async’ + ‘await’) is slightly faster on the whole.

Why is this? It could be that the ContinueWith path is not as heavily optimized as the generated asynchronous code. If you decompile an ‘async’ function, you will note that ContinueWith is in fact not used in the resulting code, but rather AwaitUnsafeOnCompleted.

Then again, let’s put this in perspective. Spending an extra 100 nanoseconds on average is eminently negligible. Your optimization energy is probably better spent elsewhere. Still, it’s good to know that there’s no shame in using ‘async’ — on the contrary, there are some perf gains to be won here.

Leave a Reply

Your email address will not be published. Required fields are marked *

Time limit is exhausted. Please reload the CAPTCHA.