{"id":106585,"date":"2022-05-05T07:00:00","date_gmt":"2022-05-05T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=106585"},"modified":"2022-05-05T07:05:02","modified_gmt":"2022-05-05T14:05:02","slug":"20220505-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220505-00\/?p=106585","title":{"rendered":"On awaiting a task with a timeout in C#"},"content":{"rendered":"<p>Say you have an awaitable object, and you want to await it, but with a timeout. How would you build that?<\/p>\n<p>What you can do is use a <code>when_any<\/code>-like function in combination with a timeout coroutine. For C# this would be something like<\/p>\n<pre>await Task.WhenAny(\r\n    DoSomethingAsync(),\r\n    Task.Delay(TimeSpan.FromSeconds(1)));\r\n<\/pre>\n<p>The <code>WhenAny<\/code> method completes as soon as any of the passed-in tasks completes. It returns the winner, which you can use to detect whether the operation completed or timed out:<\/p>\n<pre>var somethingTask = DoSomethingAsync();\r\nvar winner = await Task.WhenAny(\r\n    somethingTask,\r\n    Task.Delay(TimeSpan.FromSeconds(1)));\r\nif (winner == somethingTask)\r\n{\r\n    \/\/ hooray it worked\r\n}\r\nelse\r\n{\r\n    \/\/ sad, it timed out\r\n}\r\n<\/pre>\n<p>If the operation produced a result, you&#8217;ll have to create a timeout task that completes with the same result type, even if you never actually use that result.<\/p>\n<pre>static async Task&lt;T&gt;\r\nDelayedDummyResultTask&lt;T&gt;(TimeSpan delay)\r\n{\r\n    await Task.Delay(delay);\r\n    return default(T);\r\n}\r\n\r\nvar somethingTask = GetSomethingAsync();\r\nvar winner = await Task.WhenAny(\r\n    somethingTask,\r\n    DelayedDummyResultTask&lt;Something&gt;(TimeSpan.FromSeconds(1)));\r\nif (winner == somethingTask)\r\n{\r\n    \/\/ hooray it worked\r\n}\r\nelse\r\n{\r\n    \/\/ sad, it timed out\r\n}\r\n<\/pre>\n<p>The purpose of the <code>Delayed\u00adDummy\u00adResult\u00adTask<\/code> is not to produce a result, but rather to provide a delay.<\/p>\n<p>We can wrap this up in a helper:<\/p>\n<pre>static async Task&lt;(bool, Task&lt;T&gt;)&gt;\r\nTaskWithTimeout&lt;T&gt;(\r\n    Task&lt;T&gt; task,\r\n    TimeSpan timeout)\r\n{\r\n    var winner = await Task.WhenAny(\r\n        task, DelayedDummyResultTask&lt;T&gt;(timeout));\r\n    return (winner == task, winner);\r\n}\r\n\r\nvar (succeeded, task) = await TaskWithTimeout(\r\n    GetProgramAsync(), TimeSpan.FromSeconds(1));\r\nif (succeeded) {\r\n    UseIt(task.Result);\r\n} else {\r\n    \/\/ Timed out\r\n}\r\n<\/pre>\n<p>The usage pattern here is still rather clunky, though.<\/p>\n<p>One common pattern is to call the method, but abandon it and return some fallback value instead (typically <code>false<\/code> or <code>null<\/code>):<\/p>\n<pre>static async Task&lt;T&gt;\r\nDelayedResultTask&lt;T&gt;(TimeSpan delay, T result = default(T))\r\n{\r\n    await Task.Delay(delay);\r\n    return result;\r\n}\r\n\r\nstatic async Task&lt;T&gt;\r\nTaskWithTimeoutAndFallback&lt;T&gt;(\r\n    Task&lt;T&gt; task,\r\n    TimeSpan timeout,\r\n    T fallback = default(T))\r\n{\r\n    return (await Task.WhenAny(\r\n        task, DelayedResultTask&lt;T&gt;(timeout, fallback))).Result;\r\n}\r\n<\/pre>\n<p>This time, our delayed dummy result is no longer a dummy result. If the task times out, then the result of <code>Task.<wbr \/>WhenAny<\/code> is the timeout task, and <i>its<\/i> result is what becomes the result of the <code>Task\u00adWith\u00adTimeout\u00adAndFallback<\/code>.<\/p>\n<p>Another way of writing the above would be<\/p>\n<pre>static async Task&lt;T&gt;\r\nTaskWithTimeoutAndFallback&lt;T&gt;(\r\n    Task&lt;T&gt; task,\r\n    TimeSpan timeout,\r\n    T fallback = default(T))\r\n{\r\n    return await await Task.WhenAny(\r\n        task, DelayedResultTask&lt;T&gt;(timeout, fallback));\r\n}\r\n<\/pre>\n<p>which you might choose if only because it give you a rare opportunity to write <code>await await<\/code>.<\/p>\n<p>You could call the function like this:<\/p>\n<pre>var something = TaskWithTimeoutAndFallback(\r\n    GetSomethingAsync(), TimeSpan.FromSeconds(1));\r\n<\/pre>\n<p>The value in <code>something<\/code> is the result of <code>Get\u00adSomething\u00adAsync()<\/code> or <code>null<\/code>.<\/p>\n<p>It might be that the fallback result is expensive to calculate. For example, it <code>Get\u00adSomething\u00adAsync<\/code> times out, maybe you want to query some alternate database to get the fallback value. So maybe we could have a version where the fallback value is generated lazily.<\/p>\n<pre>static async Task&lt;T&gt;\r\nDelayedResultTask&lt;T&gt;(TimeSpan delay, Func&lt;T&gt; fallbackMaker)\r\n{\r\n    await Task.Delay(delay);\r\n    return fallbackMaker();\r\n}\r\n\r\nstatic async Task&lt;T&gt;\r\nTaskWithTimeoutAndFallback&lt;T&gt;(\r\n    Task&lt;T&gt; task,\r\n    TimeSpan timeout,\r\n    Func&lt;T&gt; fallbackMaker)\r\n{\r\n    return await await Task.WhenAny(\r\n        task, DelayedResultTask&lt;T&gt;(timeout, fallbackMaker));\r\n}\r\n\r\nvar something = TaskWithTimeoutAndFallback(\r\n    GetSomethingAsync(), TimeSpan.FromSeconds(1),\r\n    () =&gt; LookupSomethingFromDatabase());\r\n<\/pre>\n<p>As a special case, you might want to raise a <code>Timeout\u00adException<\/code> instead of a fallback value. You could do that by passing a lambda that just throws the <code>Timeout\u00adException<\/code> instead of producing a fallback value.<\/p>\n<pre>var something = TaskWithTimeoutAndFallback(\r\n    GetSomethingAsync(), TimeSpan.FromSeconds(1),\r\n    () =&gt; throw TimeoutException());\r\n<\/pre>\n<p>This is probably a common enough pattern that we could provide a special helper for it.<\/p>\n<pre>static async Task&lt;T&gt;\r\nDelayedTimeoutExceptionTask&lt;T&gt;(TimeSpan delay)\r\n{\r\n    await Task.Delay(delay);\r\n    throw new TimeoutException();\r\n}\r\n\r\nstatic async Task&lt;T&gt;\r\nTaskWithTimeoutAndException&lt;T&gt;(\r\n    Task&lt;T&gt; task,\r\n    TimeSpan timeout)\r\n{\r\n    return await await Task.WhenAny(\r\n        task, DelayedTimeoutExceptionTask&lt;T&gt;(timeout));\r\n}\r\n\r\n\/\/ throws TimeoutException on timeout\r\nvar something = TaskWithTimeoutAndFallback(\r\n    GetSomethingAsync(), TimeSpan.FromSeconds(1));\r\n<\/pre>\n<p>Note that in all of this, the task that timed out continues to run to completion. It&#8217;s just that we&#8217;re not paying attention to it any more. If you want to cancel the abandoned task, you need to hook up a task cancellation source when you create it, assuming that&#8217;s even possible.<\/p>\n<p>In the special case where the <code>Task<\/code> came from a Windows Runtime asynchronous action or operation, you can hook up the cancellation token yourself:<\/p>\n<pre>var source = new CancellationTokenSource();\r\nvar something = TaskWithTimeoutAndFallback(\r\n    o.GetSomethingAsync().AsTask(source.token),\r\n    TimeSpan.FromSeconds(1));\r\nsource.Cancel();\r\nsource.Dispose();\r\n\r\n\/\/ see what's in the \"something\"\r\n<\/pre>\n<p>If you prefer to exit with an exception, then you need to cancel the operation in your timeout handler:<\/p>\n<pre>var source = new CancellationTokenSource();\r\ntry {\r\n    var something = TaskWithTimeoutAndException(\r\n        o.GetSomethingAsync().AsTask(source.token),\r\n        TimeSpan.FromSeconds(1));\r\n} catch (TimeoutException) {\r\n    source.Cancel();\r\n} finally {\r\n    source.Dispose();\r\n}\r\n<\/pre>\n<p>That was a very long discussion, and I haven&#8217;t even gotten to the original purpose of writing about task cancellation with timeouts, which is to talk about how to do all of this in C++\/WinRT. I&#8217;m tired, so we&#8217;ll pick this up next time.<\/p>\n<p><b>Bonus reading<\/b>: <a href=\"https:\/\/devblogs.microsoft.com\/pfxteam\/crafting-a-task-timeoutafter-method\/\"> Crafting a Task.TimeoutAfter Method<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Putting something together from pieces you already have.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-106585","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Putting something together from pieces you already have.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106585","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=106585"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106585\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=106585"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=106585"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=106585"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}