警告 未等待此调用,将继续执行当前方法

本文关键字:执行 继续 方法 等待 调用 警告 | 更新日期: 2023-09-27 17:59:30

刚刚得到VS2012并试图处理async

假设我有一个从阻塞源获取一些值的方法。我不希望该方法的调用方阻止。我可以编写方法来获取值到达时调用的回调,但由于我使用的是 C# 5,因此我决定使该方法异步,这样调用者就不必处理回调:

// contrived example (edited in response to Servy's comment)
public static Task<string> PromptForStringAsync(string prompt)
{
    return Task.Factory.StartNew(() => {
        Console.Write(prompt);
        return Console.ReadLine();
    });
}

下面是调用它的示例方法。如果PromptForStringAsync不是异步的,则此方法需要在回调中嵌套回调。使用异步,我可以用这种非常自然的方式编写我的方法:

public static async Task GetNameAsync()
{
    string firstname = await PromptForStringAsync("Enter your first name: ");
    Console.WriteLine("Welcome {0}.", firstname);
    string lastname = await PromptForStringAsync("Enter your last name: ");
    Console.WriteLine("Name saved as '{0} {1}'.", firstname, lastname);
}

目前为止,一切都好。问题是当我调用 GetNameAsync 时:

public static void DoStuff()
{
    GetNameAsync();
    MainWorkOfApplicationIDontWantBlocked();
}

GetNameAsync的全部意义在于它是异步的。我不希望它被阻止,因为我想尽快回到MainWorkOfApplicationIDontWantBlocked,让GetNameAsync在后台做它的事情。但是,以这种方式调用它会在GetNameAsync行上给我一个编译器警告:

Warning 1   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.

我完全知道"当前方法的执行在调用完成之前继续"。这就是异步代码的意义所在,对吧?

我希望我的代码在没有警告的情况下编译,但这里没有什么可以"修复"的,因为代码正在做我想要做的事情。我可以通过存储 GetNameAsync 的返回值来摆脱警告:

public static void DoStuff()
{
    var result = GetNameAsync(); // supress warning
    MainWorkOfApplicationIDontWantBlocked();
}

但是现在我有多余的代码。Visual Studio似乎明白我被迫编写这个不必要的代码,因为它抑制了正常的"从未使用过的值"警告。

我还可以通过将 GetNameAsync 包装在非异步方法中来摆脱警告:

    public static Task GetNameWrapper()
    {
        return GetNameAsync();
    }

但这是更多多余的代码。所以我必须编写我不需要的代码,或者容忍不必要的警告。

我对异步的使用有什么问题吗?

警告 未等待此调用,将继续执行当前方法

如果你真的不需要结果,你可以简单地更改GetNameAsync的签名来返回void

public static async void GetNameAsync()
{
    ...
}

考虑查看相关问题的答案:返回无效和返回任务有什么区别?

更新

如果需要结果,可以更改GetNameAsync以返回,例如Task<string>

public static async Task<string> GetNameAsync()
{
    string firstname = await PromptForStringAsync("Enter your first name: ");
    string lastname = await PromptForStringAsync("Enter your last name: ");
    return firstname + lastname;
}

并按如下方式使用它:

public static void DoStuff()
{
    Task<string> task = GetNameAsync();
    // Set up a continuation BEFORE MainWorkOfApplicationIDontWantBlocked
    Task anotherTask = task.ContinueWith(r => {
            Console.WriteLine(r.Result);
        });
    MainWorkOfApplicationIDontWantBlocked();
    // OR wait for the result AFTER
    string result = task.Result;
}

我讨论得很晚,但也可以选择使用 #pragma 预处理器指令。我在这里和那里有一些异步代码,我明确不想在某些情况下等待,我不喜欢警告和未使用的变量,就像你们其他人一样:

#pragma warning disable 4014
SomeMethodAsync();
#pragma warning restore 4014

"4014"来自此 MSDN 页:编译器警告(级别 1(CS4014。

另请参阅@ryan-horath的警告/答案 这里 https://stackoverflow.com/a/12145047/928483.

在异步调用期间引发的未等待的异常将丢失。若要消除此警告,应将异步调用的 Task 返回值分配给变量。这可确保你有权访问引发的任何异常,这些异常将在返回值中指示。

C# 7.0 更新

C# 7.0 添加了一项新功能,丢弃变量:丢弃 - C# 指南,在这方面也可以提供帮助。

_ = SomeMethodAsync();

我不是特别喜欢将任务分配给未使用的变量或更改方法签名以返回 void 的解决方案。 前者创建多余的、非直观的代码,而后者可能是不可能的,如果你正在实现一个接口,或者你想使用返回的 Task 的函数。

我的解决方案是创建Task的扩展方法,称为DoNotAwait((,它什么都不做。 这不仅会抑制所有警告,无论是 ReSharp 还是其他警告,而且会使代码更易于理解,并向代码的未来维护者表明您真正希望不等待调用。

扩展方法:

public static class TaskExtensions
{
    public static void DoNotAwait(this Task task) { }
}

用法:

public static void DoStuff()
{
    GetNameAsync().DoNotAwait();
    MainWorkOfApplicationIDontWantBlocked();
}

编辑补充:这类似于Jonathan Allen的解决方案,如果尚未启动,扩展方法将启动任务,但我更喜欢使用单一用途函数,以便调用者的意图完全清楚。

async void不好!

    返回
  1. 无效和返回任务有什么区别?
  2. https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html

我的建议是,您通过匿名方法显式运行Task...

例如

public static void DoStuff()
{
    Task.Run(async () => GetNameAsync());
    MainWorkOfApplicationIDontWantBlocked();
}

或者,如果您确实希望它阻止,则可以等待匿名方法

public static void DoStuff()
{
    Task.Run(async () => await GetNameAsync());
    MainWorkOfApplicationThatWillBeBlocked();
}

但是,如果您的GetNameAsync方法必须与UI甚至任何UI绑定的交互(WINRT/MVVM,我在看你(,那么它会变得更有趣=(

您需要像这样将引用传递给 UI 调度程序...

Task.Run(async () => await GetNameAsync(CoreApplication.MainView.CoreWindow.Dispatcher));

然后在异步方法中,您需要与 UI 或 UI 绑定元素交互,认为调度程序...

dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {  this.UserName = userName; });

这是我目前正在做的事情:

SomeAyncFunction().RunConcurrently();

其中RunConcurrently定义为...

 /// <summary> 
 /// Runs the Task in a concurrent thread without waiting for it to complete. This will start the task if it is not already running. 
 /// </summary> 
 /// <param name="task">The task to run.</param> 
 /// <remarks>This is usually used to avoid warning messages about not waiting for the task to complete.</remarks> 
 public static void RunConcurrently(this Task task) 
 { 
     if (task == null) 
         throw new ArgumentNullException("task", "task is null."); 
     if (task.Status == TaskStatus.Created) 
         task.Start(); 
 } 

https://github.com/docevaad/Anchor/blob/master/Tortuga.Anchor/Tortuga.Anchor.source/shared/TaskUtilities.cs

https://www.nuget.org/packages/Tortuga.Anchor/

根据关于此警告的Microsoft文章,您可以通过简单地将返回的任务分配给变量来解决它。下面是Microsoft示例中提供的代码的翻译:

    // To suppress the warning without awaiting, you can assign the 
    // returned task to a variable. The assignment doesn't change how
    // the program runs. However, the recommended practice is always to
    // await a call to an async method.
    // Replace Call #1 with the following line.
    Task delayTask = CalledMethodAsync(delay);

请注意,这样做将导致 ReSharper 中出现"从未使用局部变量"消息。

这里有一个简单的解决方案。

public static class TasksExtensions
{
    public static void RunAndForget(this Task task)
    {
    }
}

问候

如果不想

更改方法签名以返回void(因为返回void应始终是无效的 ed(,则可以像这样使用 C# 7.0+ 丢弃功能,这比分配给变量略好(并且应该删除大多数其他源验证工具警告(:

public static void DoStuff()
{
    _ = GetNameAsync(); // we don't need the return value (suppresses warning)
    MainWorkOfApplicationIDontWantBlocked();
}
这是

导致超浮代码的简化示例。通常,您希望使用在程序中的某个点从阻塞源获取的数据,因此您希望返回结果,以便可以访问数据。

如果你真的有一些事情与程序的其余部分完全隔离,异步将不是正确的方法。只需为该任务启动一个新线程即可。

你真的要忽略结果吗? 就像包括忽略任何意外异常一样?

如果没有,你可能想看看这个问题:即发即弃的方法,

Task tResult = GetNameAsync((;

这不会产生任何错误,也无需使调用者方法异步。