16

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?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
mcs_dodo
  • 738
  • 1
  • 10
  • 17
  • 4
    Please define "interesting stuff." `Task.Run` uses a separate thread. – GSerg Sep 05 '17 at 10:54
  • 5
    Possible duplicate of [Fire-and-forget with async vs "old async delegate"](https://stackoverflow.com/questions/12803012/fire-and-forget-with-async-vs-old-async-delegate) – Liam Sep 05 '17 at 10:55
  • 4
    Tl;Dr don't use `async`/`await` if you want fire and forget. – Liam Sep 05 '17 at 10:56
  • If I remember that right, async...await allows you to do multitasking without multithreading. Especially in GUI environments, Threading has been used to implement multittasking to a inflatinary degree. But a lot of the time the action is not CPU bound wich means that tasking without threading would totally be "enough". P.S.: Fire and Forget is fine, provided you never actually forget. The last thing you want is swallowing exception in a "fire and forget" thread. Swallowing exceptions is just going to cause issues. – Christopher Sep 05 '17 at 10:57
  • 3
    No it doesn't @Christopher it's asynchronous programming as opposed to multi threaded programming. Aync reuses threads that are idle, multi threaded (`Task`) created new threads to do processing separate to the main thread. – Liam Sep 05 '17 at 10:58
  • @Liam: So it allows MultiTASKING without resorting to MultiTHREADING to implement it? Or what other meaning is there for Asynchronous programming? – Christopher Sep 05 '17 at 10:59
  • async/await parks threads that are IO bound, for re-use while it awaits for the IO process to complete. It does not create any new threads. Task creates a new thread and runs it in parallel to the existing thread. async is not multi-threading at all really, though it typically happens in a multi-threaded environment. – Liam Sep 05 '17 at 11:01
  • updated the question - what is the difference between #3 and #5 then? – mcs_dodo Sep 05 '17 at 11:06
  • 2
    In words **async** and **await** is the difference, you run new async method in separate thread that awaits for something, it doesnt freze your main thread as it is async itself – Markiian Benovskyi Sep 05 '17 at 11:09
  • @MarkBenovsky not #3 nor the #5 freezes the main thread. You probably mean the thread that is created from Task.Run. That's the answer. – mcs_dodo Sep 06 '17 at 07:24
  • Yes I meant the created thread – Markiian Benovskyi Sep 06 '17 at 07:26
  • @Christopher "multitasking" isn't really a meaningfully defined word in this context. Perhaps you mean "asynchronous"? If you had said "asynchronous", then your statements would be correct. – Servy Sep 15 '17 at 18:57
  • @MarkBenovsky That's not the case. Making the lambda to `Task.Run` asynchronous changes literally nothing. The method was *already* asynchronous. Wrapping an asynchronous method that calls another asynchronous method and returns a `Task` at the same time in the same state isn't changing anything meaningfully, just adding a tiny bit of overhead. – Servy Sep 15 '17 at 18:59
  • @Servy: Multitasking is clearly defined as the overset of all the Approaches to have multiple programms run interchangeably or even in parlel (https://en.wikipedia.org/wiki/Computer_multitasking) – Christopher Sep 16 '17 at 16:18
  • @Christopher And that's not appropriate to the context in which you used it. `await` enables *asynchronous* code. You can use an asynchronous method to create parallelism, but that's not *inherently* what it's doing, merely something that you're able to create as a result of what it *does* inherently give you (asynchrony). – Servy Sep 18 '17 at 13:14

2 Answers2

20

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.
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • So is #3 the 'correct' way to do this? Trying to determine based on my question here: https://stackoverflow.com/questions/67243044/why-cant-i-use-a-discard-with-c-sharp-async-function-in-my-web-api-project-as-a – Terry Apr 24 '21 at 13:08
  • 2
    @Terry: I don't recommend "fire and forget" at all. If you're on ASP.NET pre-Core and are OK with occasionally losing the "fire and forget" work without any logs or notifications that work has been lost, then I'd say #3 or #5 would be OK. – Stephen Cleary Apr 24 '21 at 14:02
3

"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.

Christopher
  • 9,634
  • 2
  • 17
  • 31