禁止显示“警告CS4014:因为未等待此调用,所以将继续执行当前方法…”


156

这不是“如何在不等待的情况下安全地在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此处不合适。每当我需要记录即发即弃操作的异常时,我都会使用Stephen Cleary在此提出的更为复杂的方法。


1
那么,你觉得#pragma不好吗?
弗雷德里克·哈米迪

10
@FrédéricHamidi,我知道。
noseratio 2014年

2
@Noseratio:嗯,对。抱歉,我想这是另一个警告。不理我!
乔恩·斯基特

3
@Terribad:我不太确定-在大多数情况下,警告似乎很合理。特别是,你应该想想你想发生任何故障的东西-通常即使是“射后不理”你应该制定出如何登录失败等
乔恩斯基特

4
@Terribad,在以这种方式使用它之前,应该清楚了解异步方法如何传播异常(请检查)。然后,@Knaģis的答案提供了一种优雅的方法,即通过帮助程序方法遗漏任何引发和遗忘的异常async void
noseratio 2014年

Answers:


160

使用C#7,您现在可以使用丢弃

_ = WorkAsync();

7
这是我不记得的一种方便的小语言功能。就像_ = ...我的脑子里有一个。
Marc L.

3
我发现SupressMessage从我的Visual Studio“错误列表”中删除了我的警告,但未从“输出”中删除我的警告,并且#pragma warning disable CSxxxx看起来比丢弃的东西更难看;)
David Savage

122

您可以创建一个扩展方法来防止警告。扩展方法可以为空,也可以在其中添加异常处理.ContinueWith()

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

但是,ASP.NET会计算正在运行的任务的数量,因此它不能与Forget()上面列出的简单扩展一起使用,而是可能因以下原因而失败:

异步模块或处理程序在异步操作仍挂起时完成。

使用.NET 4.5.2,可以使用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));
}

8
我发现了TplExtensions.Forget。下还有更多好处Microsoft.VisualStudio.Threading。我希望它可以在Visual Studio SDK之外使用。
noseratio 2014年

1
@Noseratio和Knagis,我喜欢这种方法,并且打算使用它。我发布了一个相关的后续问题:stackoverflow.com/questions/22864367/fire-and-forget-approach
Matt Smith

3
@stricq将ConfigureAwait(false)添加到Forget()的目的是什么?据我了解,ConfigureAwait仅影响在Task上使用await时的线程同步,但是Forget()的目的是丢弃Task,因此永远无法等待Task,因此这里的ConfigureAwait是没有意义的。
dthorpe '16

3
如果生成的线程在火灾和忘记任务完成之前就消失了,如果没有ConfigureAwait(false),它将仍然尝试将自己编组回生成的线程,该线程消失了,因此死锁了。设置ConfigureAwait(false)会告诉系统不要将其编组回调用线程。
stricq '16

2
此回复进行了编辑以管理特定案件,并附带了十几条评论。简单地说,事情往往是正确的事情,请丢弃!我引用@ fjch1997答案:愚蠢的是创建一个方法,该方法需要花更多的时间来执行,只是为了抑制警告。
Teejay

39

您可以使用以下属性修饰该方法:

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

基本上,您是在告诉编译器您知道自己在做什么,而无需担心可能的错误。

该代码的重要部分是第二个参数。“ CS4014:”部分是禁止显示警告的部分。您可以在剩下的任何地方写任何东西。


不适用于我:Visual Studio for Mac 7.0.1(内部版本24)。似乎应该,但是-不。
IronRod

1
[SuppressMessage("Compiler", "CS4014")]在“错误列表”窗口中取消显示消息,但“输出”窗口仍显示警告行
David Ching

35

我的两种处理方式。

将其保存到废弃变量(C#7)

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

自从C#7中引入丢弃功能以来,我现在认为这比禁止警告更好。因为它不仅抑制了警告,而且使开枪和遗忘的意图变得清晰。

而且,编译器将能够在发行模式下对其进行优化。

压抑它

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

是一个很好的解决方案,可以“解雇”。

存在此警告的原因是,在许多情况下,您无意使用不等待任务就返回任务的方法。当您确实打算开火并忘记时,取消警告是有道理的。

如果您不记得如何拼写#pragma warning disable 4014,只需让Visual Studio为您添加即可。按Ctrl +。打开“快速操作”,然后“抑制CS2014”

总而言之

创建一个需要花更多的时间来执行的方法是愚蠢的,只是为了消除警告。


这适用于Visual Studio for Mac 7.0.1(内部版本24)。
IronRod

1
创建一个需要花更多的时间来执行的方法是愚蠢的,仅是为了消除警告的目的 -这个方法根本不会增加额外的时间,并且IMO更易读:[MethodImpl(MethodImplOptions.AggressiveInlining)] void Forget(this Task @this) { } /* ... */ obj.WorkAsync().Forget();
noseratio

1
@Noseratio很多时候当我使用AggressiveInlining编译器时,无论出于何种原因,它都会忽略它
fjch1997

1
我喜欢pragma选项,因为它非常简单,仅适用于代码的当前行(或部分),而不适用于整个方法。
wasatchwizard

2
不要忘记使用错误代码,例如#pragma warning disable 4014,然后使用来恢复警告#pragma warning restore 4014。它仍然可以在没有错误代码的情况下工作,但是,如果您不添加错误号,它将禁止所有消息。
DunningKrugerEffect

11

停止警告的简单方法是在调用任务时简单地分配任务:

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

因此,在您的原始帖子中,您将执行以下操作:

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

我在问题本身中提到了这种方法,因为我并不特别喜欢这种方法。
noseratio

哎呀!没注意到,因为它和您的实用程序在同一代码节中。。。我一直在寻找答案。除此之外,您不喜欢这种方法吗?
noelicus

1
我不喜欢那task看起来像一个被遗忘的局部变量。几乎就像编译器应该给我另一个警告一样,类似“ task已分配但从未使用过它的值”之类的东西,除此之外没有。而且,这会使代码的可读性降低。我自己使用这种方法。
noseratio

足够公平-我有类似的感觉,这就是为什么要命名它的原因fireAndForget...因此,我希望它今后不再被引用。
noelicus

4

警告的原因是WorkAsync返回一个Task从未读取或等待的。您可以将WorkAsync的返回类型设置为void,警告将消失。

通常,Task当调用方需要知道工作程序的状态时,方法会返回a 。在“一劳永逸”的情况下,应返回空值,类似于调用者独立于被调用方法。

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

2

为什么不将其包装在返回void的异步方法中?有点冗长,但是使用了所有变量。

static async Task StartWorkAsync()
{   
     async void WorkAndForgetAsync() => await WorkAsync();
     WorkAndForgetAsync(); // no warning
}

1

我今天偶然发现了这种方法。您可以定义一个委托并将异步方法首先分配给该委托。

    delegate Task IntermediateHandler();



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

并这样称呼它

(new IntermediateHandler(AsyncOperation))();

...

我认为有趣的是,在使用委托时,编译器不会给出完全相同的警告。


无需声明委托,您也可以这样做,(new Func<Task>(AsyncOperation))()尽管IMO仍然有些冗长。
noseratio
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.