Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async/await with/without awaiting (fire and forget)

I have the following code:

static async Task Callee()
{
    await Task.Delay(1000);
}

static async Task Caller()
{
    Callee(); // #1 fire and forget
    await Callee(); // #2 >1s
    Task.Run(() => Callee()); // #3 fire and forget
    await Task.Run(() => Callee()); // #4 >1s
    Task.Run(async () => await Callee()); // #5 fire and forget
    await Task.Run(async () => await Callee()); // #6 >1s
}

static void Main(string[] args)
{
    var stopWatch = new Stopwatch();
    stopWatch.Start();
    Caller().Wait();
    stopWatch.Stop();
    Console.WriteLine($"Elapsed: {stopWatch.ElapsedMilliseconds}");
    Console.ReadKey();
}

#1 fires and forgets in the most simple way. #2 simply waits. Interesting stuff begins from #3 on. What's the in-depth logic behind the calls?

I'm aware of using fire'n'forget caveats in ASP.NET as pointed here. I'm asking this, because we're moving our app to service fabric where we no longer can use HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken => await LongMethodAsync()); and the advice is to simply replace it with Task.Run.

I see that Task.Run runs a new thread, what would be the difference between #3 and #5 then?

like image 926
mcs_dodo Avatar asked Sep 05 '17 10:09

mcs_dodo


2 Answers

I'm asking this, because we're moving our app to service fabric where we no longer can use HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken => await LongMethodAsync()); and the advice is to simply replace it with Task.Run.

That's bad advice. You should use a separate background process separated from your web frontend by a queue.

What's the in-depth logic behind the calls?

  1. Starts the asynchronous method on the current thread. Ignores all results (including exceptions).
  2. Starts the asynchronous method on the current thread. Asynchronously waits for it to complete. This is the standard way of calling asynchronous code.
  3. Starts the asynchronous method on a thread pool thread. Ignores all results (including exceptions).
  4. Starts the asynchronous method on a thread pool thread. Asynchronously waits for it to complete.
  5. Exactly the same as #3.
  6. Exactly the same as #4.
like image 57
Stephen Cleary Avatar answered Nov 16 '22 10:11

Stephen Cleary


"26. "Fire and Forget" is fine, provided you never actually forget." Maxim 26.

If you do any kind of fire and forget scenario, you have a massive risk of swallowing Exceptions. Swallowing any exception - but especially fatal ones - is a deadly sin of exception handling. All you end up is with a programm in memory that will produce even less understandable and reproduceable Exceptions. So do not ever start. Here is are two nice articles to read on the mater:

http://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx http://www.codeproject.com/Articles/9538/Exception-Handling-Best-Practices-in-NET

Full on Threads are notoriously capable of swallowing exceptions. Indeed you have to do work to not swallow them and then check if they had one after they finished. You should have at least some followup task that does logging (and possible exposure) of exceptions. Or you will really regret that "fire and forget".

Hope that helps.

like image 45
Christopher Avatar answered Nov 16 '22 08:11

Christopher