200

This is not a duplicate of "How to safely call an async method in C# without await".

How do I nicely suppress the following warning?

warning CS4014: 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.

A simple example:

static async Task WorkAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done!");
}

static async Task StartWorkAsync()
{
    WorkAsync(); // I want fire-and-forget 

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}

What I tried and did not like:

static async Task StartWorkAsync()
{
    #pragma warning disable 4014
    WorkAsync(); // I want fire-and-forget here
    #pragma warning restore 4014
    // ...
}

static async Task StartWorkAsync()
{
    var ignoreMe = WorkAsync(); // I want fire-and-forget here
    // ...
}

Updated, since the original accepted answer has been edited, I've changed the accepted answer to the one using C# 7.0 discards, as I don't think ContinueWith is appropriate here. Whenever I need to log exceptions for fire-and-forget operations, I use a more elaborate approach proposed by Stephen Cleary here.

noseratio
  • 59,932
  • 34
  • 208
  • 486
  • 2
    So, you think `#pragma` is not nice? – Frédéric Hamidi Mar 25 '14 at 09:17
  • 1
    Why have you made your method async when you're not actually doing anything asynchronous? – Jon Skeet Mar 25 '14 at 09:17
  • 13
    @FrédéricHamidi, I do. – noseratio Mar 25 '14 at 09:17
  • It's a valid warning but if you really want to ... http://msdn.microsoft.com/en-us/library/jj715718.aspx – ta.speot.is Mar 25 '14 at 09:17
  • @Noseratio: Well you're clearly not awaiting anything, otherwise you wouldn't get the warning. So the whole of your method is running synchronously, despite your use of `async`. *That* is why the compiler is warning you. So again, why is your method `async`? What do you think it's buying you? – Jon Skeet Mar 25 '14 at 09:19
  • @JonSkeet, I updated the code, hope it makes more sense now. I want fire-and-forget there, and there's still a warning because I do not await `WorkAsync()`. – noseratio Mar 25 '14 at 09:31
  • 2
    @Noseratio: Ah, right. Sorry, I'd thought it was the other warning. Ignore me! – Jon Skeet Mar 25 '14 at 09:31
  • @JonSkeet I'm interested to hear if in your opinion, do you think this could have been handled better otherwise, without suppressing the warning? – InvalidBrainException Jul 21 '14 at 09:21
  • 4
    @Terribad: I'm not really sure - it seems that the warning is pretty reasonable for most cases. In particular, you should think about what you want to happen to any failures - usually even for "fire and forget" you should work out how to log failures etc. – Jon Skeet Jul 21 '14 at 09:34
  • 1
    As the accepted answer has been edited, in its current form it no longer reflects how I'd do it. [This one does](http://stackoverflow.com/q/22864367/1768303). – noseratio Mar 09 '17 at 20:31
  • This is opinion based, one persons not nice is another's nice solution. – Liam Nov 25 '21 at 09:04
  • 1
    If the returned task is discarded everywhere it is used, perhaps then you should consider making the fire and forget method return `void` instead of a `Task`. – tvandinther May 26 '22 at 23:13
  • 1
    @tvandinther if you make it `async void`, it will be throwing any unhandled exceptions via SynchronizationContext.Post or Thread.QueueUserWorkItem "out-of-band", which may or may not be what you want. – noseratio May 26 '22 at 23:55

8 Answers8

267

With C# 7 you can now use discards:

_ = WorkAsync();
Anthony Wieser
  • 4,351
  • 1
  • 23
  • 25
  • 21
    This is a handy little language feature that I just cannot remember. It's like there's a `_ = ...` in my brain. – Marc L. Mar 14 '18 at 14:29
  • 3
    I found a SupressMessage removed my warning from my Visual Studio "Error List" but not "Output" and `#pragma warning disable CSxxxx` looks more ugly than the discard ;) – David Savage Aug 24 '18 at 09:11
125

You can create an extension method that will prevent the warning. The extension method can be empty or you can add exception handling with .ContinueWith() there.

static class TaskExtensions
{
    public static void Forget(this Task task)
    {
        task.ContinueWith(
            t => { WriteLog(t.Exception); },
            TaskContinuationOptions.OnlyOnFaulted);
    }
}

public async Task StartWorkAsync()
{
    this.WorkAsync().Forget();
}

However ASP.NET counts the number of running tasks, so it will not work with the simple Forget() extension as listed above and instead may fail with the exception:

An asynchronous module or handler completed while an asynchronous operation was still pending.

With .NET 4.5.2 it can be solved by using HostingEnvironment.QueueBackgroundWorkItem:

public static Task HandleFault(this Task task, CancellationToken cancelToken)
{
    return task.ContinueWith(
        t => { WriteLog(t.Exception); },
        cancelToken,
        TaskContinuationOptions.OnlyOnFaulted,
        TaskScheduler.Default);
}

public async Task StartWorkAsync()
{
    System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(
        cancelToken => this.WorkAsync().HandleFault(cancelToken));
}
Community
  • 1
  • 1
Knaģis
  • 20,827
  • 7
  • 66
  • 80
  • 8
    I've found [`TplExtensions.Forget`](http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.threading.tplextensions.forget.aspx). There's a lot more goodness under [`Microsoft.VisualStudio.Threading`](http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.threading.aspx). I wish it was made available for use outside Visual Studio SDK. – noseratio Mar 25 '14 at 10:11
  • 1
    @Noseratio, and Knagis, I like this approach and I plan on using it. I posted a related follow-up question: http://stackoverflow.com/questions/22864367/fire-and-forget-approach – Matt Smith Apr 04 '14 at 13:37
  • 1
    I would also suggest adding task.ConfigureAwait(false); inside the Forget method. Or at least passing through a bool to ConfigureAwait. – stricq Aug 30 '15 at 23:50
  • 3
    @stricq What purpose would adding ConfigureAwait(false) to Forget() serve? As I understand it, ConfigureAwait only affects thread sync at the point where await is used on a Task, but the purpose of Forget() is to throw away the Task, so the Task can never be awaited, so ConfigureAwait here is pointless. – dthorpe Feb 02 '16 at 18:25
  • 3
    If the spawning thread goes away before the fire and forget task is complete, without ConfigureAwait(false) it will still try to marshall itself back onto the spawning thread, that thread is gone so it deadlocks. Setting ConfigureAwait(false) tells the system not to marshall back onto the calling thread. – stricq Feb 02 '16 at 18:41
  • Hi @Knaģis, I just noticed your latest edits. I must admit I liked your [original version](http://stackoverflow.com/revisions/22630057/1) much more for its simplicity, because it clearly assumes the `Task` code's intention to handle its internal exceptions/completion etc. Otherwise, the following would be more appropriate, IMO: `static async void Forget(this Task task) { await task; /* or await within try/catch */ }` as it works with the current synchronisation context and posts unhandled exceptions there. – noseratio Jan 25 '17 at 23:32
  • @Noseratio - the original text did include a hint about the exception handling, I just was too lazy back then to add the example. I suspect that many who used this trick did not think about the exceptions at all. – Knaģis Jan 26 '17 at 11:16
  • Hey @Knaģis, given how popular this is, could you add `TaskContinuationOptions.ExecuteSynchronously` flag to your current version? Just a small optimiation that potentially saves a thread switch. – noseratio Aug 29 '17 at 12:01
  • @Noseratio - have you measured that the flag actually gives improvement? I suspect that would depend on how the exception is handled - for example, if it logs it to a database or file, the synchronous approach might not be preferable. – Knaģis Sep 17 '17 at 09:04
  • I love this solution more than discards – dexiang May 06 '18 at 15:41
  • 2
    This reply has an edit to manage a specific case, plus a dozen of comments. Simply things are often the right things, go for discards! And I quote @fjch1997 answer: **It's stupid to create a method that takes a few more ticks to execute, just for the purpose of suppressing a warning.** – Teejay Jun 15 '18 at 12:41
60

My two way of dealing with this.

Save it to a discard variable (C# 7)

Example

_ = Task.Run(() => DoMyStuff()).ConfigureAwait(false);

Since the introduction of discards in C# 7, I now consider this to be better than supressing the warning. Because it not only supresses the warning, but also makes the fire-and-forget intention clear.

Moreover, the compiler will be able to optimize it away in release mode.

Just suppress it

#pragma warning disable 4014
...
#pragma warning restore 4014

is a nice enough solution to "fire and forget".

The reason why this warning exists is because in many cases it is not your intention to use an method that returns task without awaiting it. Suppressing the warning when you do intend to fire and forget makes sense.

If you have trouble remembering how to spell #pragma warning disable 4014, simply let Visual Studio add it for you. Press Ctrl+. to open "Quick Actions" and then "Suppress CS2014"

All in all

It's stupid to create a method that takes a few more ticks to execute, just for the purpose of suppressing a warning.

fjch1997
  • 1,518
  • 18
  • 19
  • This worked in Visual Studio for Mac 7.0.1 (build 24). – AdvApp Jun 06 '17 at 17:00
  • 1
    *It's stupid to create a method that takes a few more ticks to execute, just for the purpose of suppressing a warning* - this one doesn't add extra ticks at all and IMO is more readable: `[MethodImpl(MethodImplOptions.AggressiveInlining)] void Forget(this Task @this) { } /* ... */ obj.WorkAsync().Forget();` – noseratio Jun 09 '17 at 09:40
  • 1
    @Noseratio A lot of times when I use `AggressiveInlining` the compiler just ignores it for whatever reason – fjch1997 Jun 27 '17 at 15:57
  • 1
    I like the pragma option, because it is super simple and only applies to the current line (or section) of code, not a whole method. – kodybrown Nov 02 '17 at 16:55
  • 2
    Don't forget to use the error code like `#pragma warning disable 4014` and then to restore the warning afterward with `#pragma warning restore 4014`. It still works without the error code, but if you don't add the error number it'll suppress all messages. – DunningKrugerEffect Jun 28 '18 at 14:44
39

You can decorate the method with the following attribute:

[System.Diagnostics.CodeAnalysis.SuppressMessage("Await.Warning", "CS4014:Await.Warning")]
static async Task StartWorkAsync()
{
    WorkAsync();
    // ...
}

Basically you are telling the compiler that you know what you are doing and it does not need to worry about possible mistake.

The important part of this code is the second parameter. The "CS4014:" part is what suppresses the warning. You can write anything you want on the rest.

Fábio
  • 505
  • 5
  • 9
  • Doesn't work for me: Visual Studio for Mac 7.0.1 (build 24). Seems like it should but -- nope. – AdvApp Jun 06 '17 at 16:57
  • 1
    `[SuppressMessage("Compiler", "CS4014")]` suppresses the message in the Error List window, but the Output window still shows a warning line – David Ching Aug 24 '17 at 12:48
13

An easy way of stopping the warning is to simply assign the Task when calling it:

Task fireAndForget = WorkAsync(); // No warning now

And so in your original post you would do:

static async Task StartWorkAsync()
{
    // Fire and forget
    var fireAndForget = WorkAsync(); // Tell the compiler you know it's a task that's being returned 

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}
noelicus
  • 14,468
  • 3
  • 92
  • 111
  • I mentioned this approach in the question itself, as one of those I didn't particularly like. – noseratio Mar 09 '17 at 20:20
  • Whoops! Did not notice that because it was in the same code section as your pragma one... And I was looking for answers. Aside from that, what is it you don't like about this method? – noelicus Mar 10 '17 at 08:00
  • 1
    I don't like that `task` looks like an forgotten local variable. Almost like the compiler should give me another warning, something like "`task` is assigned but its value is never used", besides it doesn't. Also, it makes the code less readable. I myself use [this](http://stackoverflow.com/q/22864367/1768303) approach. – noseratio Mar 10 '17 at 10:15
  • 1
    Fair enough - I had a similar feeling which is why I name it `fireAndForget` ... so I expect it to be henceforth unreferenced. – noelicus Sep 27 '17 at 13:48
8

The reason for the warning is WorkAsync is returning a Task that is never read or awaited. You can set the return type of WorkAsync to void and the warning will go away.

Typically a method returns a Task when the caller needs to know the status of the worker. In the case of a fire-and-forget, void should be returned to resemble that the caller is independent of the called method.

static async void WorkAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done!");
}

static async Task StartWorkAsync()
{
    WorkAsync(); // no warning since return type is void

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}
S.Serpooshan
  • 7,608
  • 4
  • 33
  • 61
Victor
  • 81
  • 1
  • 2
2

Why not wrap it inside an async method that returns void ? A bit lengthy but all variables are used.

static async Task StartWorkAsync()
{   
     async void WorkAndForgetAsync() => await WorkAsync();
     WorkAndForgetAsync(); // no warning
}
Akli
  • 1,351
  • 1
  • 15
  • 28
1

I found this approach by accident today. You can define a delegate and assign the async method to the delegate first.

    delegate Task IntermediateHandler();



    static async Task AsyncOperation()
    {
        await Task.Yield();
    }

and call it like so

(new IntermediateHandler(AsyncOperation))();

...

I thought it was interesting that the compiler wouldn't give the exact same warning when using the delegate.

David Beavon
  • 1,141
  • 9
  • 16
  • No need to declare a delegate, you might as well do `(new Func(AsyncOperation))()` although IMO it's still a bit too verbose. – noseratio Dec 06 '18 at 12:16