Considerations for not awaiting a Task in an asynchronous method

8.2k views Asked by At

I'm working on a Web API project which uses Azure's managed cache service to cache database results in memory to improve response times and alleviate duplicate traffic to the database. When attempting to put a new item in the cache, occasionally a cache-specific exception will be thrown with a code of DataCacheErrorCode.RetryLater. Naturally, in order to retry later without needing to block on this method I made it async and await Task.Delay to try again a short time later. Previously a developer had hardcoded a Thread.Sleep in there that was really hurting application performance.

The method signature looks something similar to this now:

public static async Task Put(string cacheKey, object obj)

Following this change I get ~75 compiler warnings from all the other places in the application that called the formerly synchronous version of Put indicating:

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

In this case, since Put doesn't return anything, it makes sense to me to let this operation fire-and-forget as I don't see any reason to block execution of the method that called it. I'm just wondering if there are any dangers or pitfalls for allowing a lot of these fire-and-forget Tasks running in the background as Put can be called quite often. Or should I await anyway since 99% of the time I won't get the retry error and the Task will finish almost immediately. I just want to make sure that I'm not incurring any penalties for having too many threads (or something like that).

4

There are 4 answers

12
Yuval Itzchakov On BEST ANSWER

If there is a chance Put will throw any other exception for any kind of reason, and you don't use await Put each time you're inserting an object to the cache, the exceptions will be swallowed inside the returned Task which isn't being awaited. If you're on .NET 4.0, this exception will be re-thrown inside the Finalizer of that Task.. If you're using .NET 4.5, it will simply be ignored (and that might not be desirable).

Want to make sure that I'm not incurring any penalties for having too many threads or something like that.

Im just saying this to make things clear. When you use Task.Delay, you aren't spinning any new threads. A Task isn't always equal to a new thread being spun. Specifically here, Task.Delay internally uses a Timer, so there aren't any thread overheads (except for the thread which is currently being delayed if you do use await).

11
Servy On

The warning is telling you that you're getting fire and forget behavior in a location where you may not actually want to fire and forget. If you really do want to fire and forget and don't have a problem continuing on without ever knowing when the operation finishes, or if it even finished successfully, then you can safely ignore the warning.

1
Avner Shahar-Kashtan On

One negative result of releasing a task to run unawaited is the compiler warnings themselves - 75 compiler warnings are a problem in and of themselves and they hide real warnings.

If you want to signal the compiler that you're intentionally not doing anything with the result of the task, you can use a simple extension method that does nothing, but satisfies the compiler's desire for explicitness.

// Do nothing.
public static void Release(this Task task)
{
}

Now you can call

UpdateCacheAsync(data).Release();

without any compiler warnings.

https://gist.github.com/lisardggY/396aaca7b70da1bbc4d1640e262e990a

0
Yuri On

Recommended ASP.NET way is

HostingEnvironment.QueueBackgroundWorkItem(WorkItem);

...

async Task WorkItem(CancellationToken cancellationToken)
{
    try { await ...} catch (Exception e) { ... }
}

BTW Not catching/re-throw on thread other then ASP.NET thread can cause server process to be crashed/restarted