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

刚拿到 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);
}

到目前为止一切顺利。问题是当我 打电话获得名字同步时:

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

GetNameAsync的关键在于它是异步的。我不用 想要来阻塞它,因为我想尽快回到 MainWorkOfApplicationIDontWantBlockedASAP,让 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();
}

但现在我有多余的代码了。VisualStudio 似乎理解我是被迫编写这些不必要的代码的,因为它抑制了正常的“值从未使用”警告。

我还可以通过将 GetNameAsync 包装在一个非异步的方法中来消除这个警告:

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

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

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

88300 次浏览

正是您简化的示例导致了多余的代码。通常,您希望使用在程序中某个位置从阻塞源获取的数据,因此您希望返回结果,以便能够获取数据。

如果您真的有一些与程序其余部分完全隔离的事情发生,那么异步将不是正确的方法。为那个任务启动一个新线程。

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

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

考虑看看相关问题的答案: 返回 void 和返回 Task 有什么区别?

更新

如果你需要结果,你可以改变 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;
}

根据 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 中出现“ Local variable is never used”消息。

这就是我目前正在做的:

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 锚/特图加锚源/共享/ taskutilities.cs

Https://www.nuget.org/packages/tortuga

async void坏了!

  1. 返回 void 和返回 Task 的区别是什么?
  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; });

我已经很晚才开始讨论这个问题,但是还可以选择使用 #pragma预处理器指令。我有一些异步代码在这里和那里,我明确地不想等待在一些条件,我不喜欢警告和未使用的变量,就像你们其余的人:

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

"4014"来自这个 MSDN 页面: 编译器警告(级别1) CS4014

另见@ryan-horath 的警告/回答。

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

C # 7.0更新

C # 7.0增加了一个新的特性,丢弃变量: 弃牌 C # 指南,这在这方面也有帮助。

_ = SomeMethodAsync();

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

我的解决方案是创建一个 Task 的扩展方法,名为 DoNotAwait () ,该方法不执行任何操作。这不仅可以禁止所有警告(ReSharper 或其他) ,而且可以使代码更容易理解,并向代码的未来维护人员表明您确实希望不要等待调用。

扩展方法:

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

用法:

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

编辑添加: 这类似于 Jonathan Allen 的解决方案,扩展方法将启动任务(如果还没有启动) ,但我更喜欢具有单一用途的功能,这样调用者的意图就完全清楚了。

您真的想忽略结果吗? 比如忽略任何意外的异常?

如果没有,你可能想看看这个问题: 火与忘记的方法,

给,一个简单的解决办法。

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

问候

如果你不想改变返回 void的方法签名(因为返回的 void应该总是 a无效ed) ,你可以像这样使用 C # 7.0 + 丢弃功能,这比赋值给变量稍微好一点(并且应该删除大多数其他源验证工具的警告) :

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

TasktResult = GetNameAsync () ;

这不会产生任何错误,也不需要将调用方法作为异步。