DEV Community

loading...

Why you should not use “ContinueWith” in your async code

Joni 【ジョニー】
Full-Stack Web Dev. Early adopter. C#/.NET technology lover.
Originally published at Medium on ・2 min read

This post originally appeared on Medium

Are you using ContinueWith in your async code?

For my future reference, this post is a quick summary of ASP.NET Core Architect David Fowler’s tweets:

So, here we go.

  • Async state machines, though they have creation overhead are easier to debug. They can and will be further optimized in the future (on the runtime side).
  • They execute synchronously if the task is already complete, something that ContinueWith doesn’t do this unless you specify the right set of flags.
  • Calling ContinueWith allocates another task per operation (it wraps your delegate in a task object) instead of re-using the state machine instance as the continuation. So you’re static callback is then wrapped in a ContinuationTask object. That then also gets wrapped in another continuation object and attached to the list of Task continuations…. Task itself is also is optimized for async/await over everything else.

For example:

if (!task.IsCompleted)
{
  return FinishAsync(task);
}

private async Task FinishAsync(Task task)
{
  try
  {
    await task;
  }
  catch (Exception ex)
  {
    Log(.....)
  }
}
  • ContinueWith allocates more than using async await. In fact, in .NET Core Task is very optimized for async await code paths and allocates less than ContinueWith.
  • The state machine’s overhead is a concern when you finish synchronously, not asynchronously.
  • If you take into account the whole “cost,” including the state machine generation, async/await is still lighter than just using ContinueWith in this situation. At least on .NET Core it is.
  • ContinueWith needs to capture the execution context. That’s going to mean at least an object that has both your callback and _ _options.
  • In the async await case, the state machine starts off as a struct and once you go async it is then boxed into an object, that boxing allocation is basically reused for everything.

Last but not least, also worth to check tweet thread:

Check out this Async Guidance from David Fowler ✨

Summary

Use async/await.

Discussion (0)