3

I have an async call (DoAsyncWork()), that I would like to start in a fire-and-forget way, i.e. I'm not interesting in its result and would like the calling thread to continue even before the async method is finished.

What is the proper way to do this? I need this in both, .NET Framework 4.6 as well as .NET Core 2, in case there are differences.

public async Task<MyResult> DoWorkAsync(){...}

public void StarterA(){
    Task.Run(() => DoWorkAsync());
}

public void StarterB(){
    Task.Run(async () => await DoWorkAsync());
}

Is it one of those two or something different/better?

//edit: Ideally without any extra libraries.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
silent
  • 14,494
  • 4
  • 46
  • 86

2 Answers2

7

What is the proper way to do this?

First, you need to decide whether you really want fire-and-forget. In my experience, about 90% of people who ask for this actually don't want fire-and-forget; they want a background processing service.

Specifically, fire-and-forget means:

  1. You don't care when the action completes.
  2. You don't care if there are any exceptions when executing the action.
  3. You don't care if the action completes at all.

So the real-world use cases for fire-and-forget are astoundingly small. An action like updating a server-side cache would be OK. Sending emails, generating documents, or anything business related is not OK, because you would (1) want the action to be completed, and (2) get notified if the action had an error.

The vast majority of the time, people don't want fire-and-forget at all; they want a background processing service. The proper way to build one of those is to add a reliable queue (e.g., Azure Queue / Amazon SQS, or even a database), and have an independent background process (e.g., Azure Function / Amazon Lambda / .NET Core BackgroundService / Win32 service) processing that queue. This is essentially what Hangfire provides (using a database for a queue, and running the background process in-proc in the ASP.NET process).

Is it one of those two or something different/better?

In the general case, there's a number of small behavior differences when eliding async and await. It's not something you would want to do "by default".

However, in this specific case - where the async lambda is only calling a single method - eliding async and await is fine.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 1
    thanks for your answer! In fact, my case is the one you mention to be ok: Updating a cache ;) – silent Apr 20 '20 at 11:44
2

It depends on what you mean by proper :)

For instance: are you interested in the exceptions being thrown in your "fire and forget" calls? If not, than this is sort of fine. Though what you might need to think about is in what environment the task lives.

For instance, if this is a asp.net application and you do this inside the lifetime of a thread instantiated due to a call to a .aspx or .svc. The Task becomes a background thread of that (foreground)thread. The foreground thread might get cleaned up by the application pool before your "fire and forget" task is completed.

So also think about in which thread your tasks live.

I think this article gives you some useful information on that: https://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx

Also note that if you do not return a value in your Tasks, a task will not return exception info. Source for that is the ref book for microsoft exam 70-483 There is probably a free version of that online somewhere ;P https://www.amazon.com/Exam-Ref-70-483-Programming-C/dp/0735676828

Maybe useful to know is that if your have an async method being called by a non-async and you wish to know its result. You can use .GetAwaiter().GetResult().

Also I think it is important to note the difference between async and multi-threading.

Async is only useful if there are operations that use other parts of a computer that is not the CPU. So things like networking or I/O operations. Using async then tells the system to go ahead and use CPU power somewhere else instead of "blocking" that thread in the CPU for just waiting for a response.

multi-threading is the allocation of operations on different threads in a CPU (for instance, creating a task which creates a background thread of the foreground thread... foreground threads being the threads that make up your application, they are primary, background threads exist linked to foreground threads. If you close the linked foreground thread, the background thread closes as well) This allows the CPU to work on different tasks at the same time.

Combining these two makes sure the CPU does not get blocked up on just 4 threads if it is a 4 thread CPU. But can open more while it waits for async tasks that are waiting for I/O operations.

I hope this gives your the information needed to do, what ever it is you are doing :)

  • thanks for the insights! Yes, I'm not interested in any exceptions (there is exception handling inside the DoWorkAsync). And yes, all this is within a ASP.NET app. Does this change your answer? – silent Apr 20 '20 at 07:03
  • 1
    Not really, you can skip the part about the return value. That is not needed then :) But I do like to highlight the part about background threads in foreground threads. My company had decided to use a fire and forget approach to make extra calls to azure application insights inside of the thread lifetime on a aspx page. And were surprised not allot got logged. That was because the task was instantiated just before the end of operations of that page. which meant that both the thread and the just created task got cleaned up by the application pool. So do look at the link i posed on that – Davey van Tilburg Apr 20 '20 at 07:06
  • thanks. For the ASP.NET part I'm going with queuebackgroundworkitem from Scotts post – silent Apr 20 '20 at 14:57