Asynchronously Wait for Task to Complete with Timeout

with No Comments

I was recently working at an async method which could possibly hang if some dependent stuff did not happen. To prevent the method from hanging, I wanted to implement some kind of timeout. Now, how can I make a task abort, given the following method signature?

Take One – Task Extension Method

The plan was to implement som kind of extension method to Task which could add the timeout-functionality.

And use it like this.

This design allowed that the internal Delay-task would be cancelled if the user of my API canceled the method call. Nice! But it also had some mayor disadvantages:

  • No task will be canceled if either of them finish successfully, which leads to having tasks running in the background for no reason, eating system resources.
  • I had to make sure to pass the same cancellation token both to DoStuffInnerAsync and TimeoutAfter, which might be something that could lead to mistakes further down.

Take Two – Expanding the Extension Method

To be able to cancel the TimeoutAfter-task i needed a CancellationTokenSource-instance, and pass its token to the TimeoutAfter-method. And I also wanted the TimeoutAfter-task to cancel if the user canceled the public API call.

This is what I came up with.

This is some seriously dangerous programming.

  • By subscribing to cancel-events with ct.Register(...) I opened upp the possibility for memory leaks if I do not unsubscribe somehow.
  • Also, using cts (which can be disposed) in the delegate passed to ct.Register(...) might actually make my application crash if ct was canceled outside of the TimeOutAfter-method scope.

Register returns a disposable something, which when disposed will unsubscribe. By adding the inner using-block, I fixed both of these problems.

This made it possible to cancel the Delay-task when the actual task completed, but not the reverse. How should I solve the bigger problem, how to cancel the actual task if it would hang? Eating up system resources indefinitely…

Take Three – Changing the Public API

With much hesitation I finally decided to make a breaking change to the public API by replacing the CancellationToken with a CancellationTokenSource in the DoStuffAsync-method.

Nice! But this still did not solve that I had make sure to pass the same cts to both the actual task and the Delay-task.

Final Solution – Doing the Obvious

Most things is really easy when you know the answer. By accident I stumbled upon that CancellationTokenSource has a CancelAfter(...)-method. This solves my problem entirely without the need to update my public API.

Easy peasy. I wish I had known about this earlier!

Follow Johan Classon:

Azure enthusiast, .Net Developer, PowerShell empowered DevOps hacker, and Solutions Architect.

Leave a Reply