抑制"警告CS4014:由于未等待此调用,当前方法将继续执行.."

这不是“如何在C#中安全地调用异步方法而不等待”的副本。

如何很好地抑制以下警告?

警告CS4014:由于未等待此调用,当前方法的执行将在调用完成之前继续。考虑将“ await ”操作符应用于调用结果。

一个简单的例子:

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);
}

我尝试过但不喜欢的是:

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
// ...
}

已更新,由于接受的原始答案已被编辑,我已将接受的答案改为使用C#7.0的那个丢弃了,因为我认为ContinueWith在这里不合适。每当我需要记录“即发即弃”操作的异常时,我都会使用一种更精细的方法,由斯蒂芬·克利里提出。

70457 次浏览

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));
}

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.

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.

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);
}

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);
}

With C# 7 you can now use discards:

_ = WorkAsync();

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
}

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.