Asynchronously Wait for Task to Complete with Timeout

with 1 Comment

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.

One Response

  1. Andreas
    | Reply

    Hi,

    Thank you for sharing your journey to the obvious. I saw the method and searched for some solution to extend a task to wrap the timeout in a extension method. It seems you can just put the cancellation token in the constructor.

    But one thing I am wondering is what happens if your task finishes before the timeout. Will it stop the cancellation token source?

    The CancellationTokenSource is implementing the IDisposable interface. So maybe it would be stopped if you dispose it after your await or use the using construct.

    Regards,

    Andreas

Leave a Reply