ChatGPT解决这个技术问题 Extra ChatGPT

抑制“警告 CS4014:因为不等待此调用,当前方法的执行继续......”

这不是 "How to safely call an async method in C# without await" 的副本。

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

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

一个简单的例子:

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

更新,由于 original accepted answer 已被编辑,我已将接受的答案更改为 the one using C# 7.0 discards,因为我认为 ContinueWith 在这里不合适。每当我需要记录即发即弃操作的异常时,我都会使用更精细的方法 proposed by Stephen Cleary here

那么,您认为 #pragma 不好?
当您实际上没有做任何异步操作时,为什么要让您的方法异步?
@FrédéricHamidi,我愿意。
@Noseratio:啊,对。抱歉,我还以为是另一个警告。不理我!
@Terribad:我不太确定-对于大多数情况,警告似乎是相当合理的。特别是,您应该考虑您希望对任何故障发生什么 - 通常即使对于“即发即弃”,您也应该弄清楚如何记录故障等。

A
Anthony Wieser

在 C# 7 中,您现在可以使用 discards

_ = WorkAsync();

这是一个方便的小语言功能,我只是不记得了。就像我的大脑中有一个_ = ...
我发现一个 SupressMessage 从我的 Visual Studio“错误列表”中删除了我的警告,但没有从“输出”中删除,并且 #pragma warning disable CSxxxx 看起来比丢弃更难看;)
C
Community

您可以创建一个可以防止警告的扩展方法。扩展方法可以为空,或者您可以在此处使用 .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));
}

我找到了 TplExtensions.ForgetMicrosoft.VisualStudio.Threading 下还有很多优点。我希望它可以在 Visual Studio SDK 之外使用。
@Noseratio 和 Knagis,我喜欢这种方法,我打算使用它。我发布了一个相关的后续问题:stackoverflow.com/questions/22864367/fire-and-forget-approach
@stricq 将 ConfigureAwait(false) 添加到 Forget() 的目的是什么?据我了解,ConfigureAwait 只影响在Task 上使用await 的那一点的线程同步,但Forget() 的目的是丢弃Task,因此永远无法等待Task,所以这里的ConfigureAwait 毫无意义。
如果生成线程在 fire and forget 任务完成之前就消失了,如果没有 ConfigureAwait(false),它仍然会尝试将自己编组回生成线程,该线程已经消失,因此死锁。设置 ConfigureAwait(false) 告诉系统不要编组回调用线程。
这个回复有一个编辑来管理一个特定的案例,还有一打评论。简单的事情往往是正确的事情,去丢弃!我引用@fjch1997 的回答:创建一个需要更多时间来执行的方法是愚蠢的,只是为了抑制警告。
f
fjch1997

我的两种处理方式。

将其保存到丢弃变量 (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”

总而言之

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


这适用于 Mac 7.0.1(内部版本 24)的 Visual Studio。
仅仅为了抑制警告而创建一个需要更多滴答声才能执行的方法是愚蠢的 - 这个根本不会添加额外的滴答声并且 IMO 更具可读性:[MethodImpl(MethodImplOptions.AggressiveInlining)] void Forget(this Task @this) { } /* ... */ obj.WorkAsync().Forget();
@Noseratio 很多时候,当我使用 AggressiveInlining 时,无论出于何种原因,编译器都会忽略它
我喜欢 pragma 选项,因为它超级简单,只适用于当前行(或部分)的代码,而不是整个方法。
不要忘记使用 #pragma warning disable 4014 之类的错误代码,然后使用 #pragma warning restore 4014 恢复警告。它仍然可以在没有错误代码的情况下工作,但如果您不添加错误编号,它将抑制所有消息。
T
Tim Cooper

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

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

基本上你是在告诉编译器你知道你在做什么,它不需要担心可能的错误。

这段代码的重要部分是第二个参数。 “CS4014:”部分是抑制警告的原因。你可以在其余部分写任何你想要的东西。


不适用于我:Visual Studio for Mac 7.0.1(内部版本 24)。似乎应该但是——不。
[SuppressMessage("Compiler", "CS4014")] 抑制错误列表窗口中的消息,但输出窗口仍显示警告行
n
noelicus

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

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

我在问题本身中提到了这种方法,作为我不特别喜欢的方法之一。
哎呀!没有注意到,因为它与您的 pragma one 在同一代码部分中......我正在寻找答案。除此之外,您不喜欢这种方法的什么地方?
我不喜欢 task 看起来像一个被遗忘的局部变量。几乎就像编译器应该给我另一个警告一样,“task 已分配,但它的值从未使用过”,除此之外它没有。此外,它使代码的可读性降低。我自己使用 this 方法。
很公平 - 我有类似的感觉,这就是为什么我将它命名为 fireAndForget ......所以我希望它从此不再被引用。
S
S.Serpooshan

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

通常,当调用者需要知道工作人员的状态时,方法会返回 Task。在“即发即弃”的情况下,应返回 void 以使调用者独立于被调用的方法。

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

A
Akli

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

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

D
David Beavon

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

    delegate Task IntermediateHandler();



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

并这样称呼它

(new IntermediateHandler(AsyncOperation))();

...

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


无需声明委托,您不妨这样做 (new Func<Task>(AsyncOperation))() 尽管 IMO 它仍然有点过于冗长。