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


135

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

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

关于异步的使用我在这里有问题吗?


2
顺便说一句,实施时PromptForStringAsync您要做的工作比您需要做的要多;只返回的结果Task.Factory.StartNew。值就是在控制台中输入的字符串已经是一项任务。无需等待它返回结果。这样做不会增加任何新价值。
Servy

Wwouldn't更有意义的GetNameAsync提供,是由用户提供(即全名Task<Name>,而不是只返回一个TaskDoStuff然后可以存储任务,无论await之后的另一种方法,甚至是任务传递给其他方法,因此它可以awaitWait它里面的落实地方。
Servy

@Servy:如果我只返回Task,则会收到错误消息“由于这是一个异步方法,因此返回表达式必须为'string'类型,而不是'Task <string>'”。
Mud

1
删除async关键字。
Servy

13
IMO,这对于C#团队来说是一个糟糕的警告选择。应该警告几乎肯定是错误的事情。在很多情况下,您想“触发并忘记”一个异步方法,而在其他情况下,您实际上确实想等待它。
MgSam 2013年

Answers:


103

如果您确实不需要结果,则只需更改GetNameAsync的签名即可返回void

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

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

更新资料

如果需要结果,可以将更GetNameAsync改为return,例如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;
}

15
只有在他从不关心结果的情况下才是这种情况,而不是这一次不需要结果。
Servy

3
@Servy,对,谢谢您的澄清。但是OP GetNameAsync不会返回任何值(当然,结果本身除外)。
Nikolay Khil

2
正确,但是通过返回任务,他可以知道何时完成执行所有异步操作。如果返回void,则他无法知道何时完成。这就是我在先前的评论中说“结果”时的意思。
Servy

29
通常,async void除了事件处理程序外,永远不要有方法。
丹尼尔·曼

2
这里要注意的另一件事是,当您切换到async void未捕获的任何异常时,对于未观察到的异常,行为将有所不同,但在.net 4.5中它将继续运行。
Caleb Vear 2015年

62

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

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

"4014"来源于此MSDN页:编译器警告(等级1)CS4014

另请参阅@ ryan-horath的警告/答案,网址为https://stackoverflow.com/a/12145047/928483

在未等待的异步调用过程中引发的异常将丢失。要摆脱此警告,应将异步调用的任务返回值分配给变量。这样可以确保您可以访问抛出的任何异常,这些异常将在返回值中指示。

C#7.0更新

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

_ = SomeMethodAsync();

5
太棒了 我不知道你能做到这一点。谢谢!
Maxim Gershkovich

1
甚至不需要说var,只需写_ = SomeMethodAsync();
Ray

41

我不特别喜欢将任务分配给未使用的变量,或者更改方法签名以返回void的解决方案。前者会创建多余的,非直觉的代码,而如果您要实现接口或要在其他函数中使用返回的任务,则后者可能是不可能的。

我的解决方案是创建一个名为DoNotAwait()的Task扩展方法,该方法什么都不做。这不仅会抑制所有警告,无论是ReSharper还是其他方式,都会使代码更易于理解,并向代码的未来维护者指示您确实打算等待该调用。

扩展方式:

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

用法:

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

编辑添加:这类似于乔纳森·艾伦(Jonathan Allen)的解决方案,在该解决方案中,如果扩展方法尚未启动,则扩展方法将启动任务,但我更喜欢具有单一用途的功能,以使调用者的意图完全清楚。


1
我喜欢这个,但我将其重命名为Unawait();)
Ostati,

29

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

您的任务扩展很棒。不知道为什么我以前没有实现过。
MikaelDúiBolinder

8
您提到的第一种方法会产生不同的警告:This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. 这还会导致创建新线程,而不一定要单独使用async / await创建新线程。
gregsdennis 2015年

先生,你应该起来!
奥兹坎

16

这是我目前正在做的:

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/


1
+1。这样看来可以避免在其他答案的注释中比较所有问题。谢谢。
Grault 2014年

3
您只需要这样: public static void Forget(this Task task) { }
Shital Shah 2014年

1
@ShitalShah是什么意思
Jay Wick

@ShitalShah仅适用于自动启动任务,例如使用创建的任务async Task。有些任务需要手动启动。
乔纳森·艾伦

7

根据有关此警告的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中出现“从不使用本地变量”消息。


是的,它可以消除警告,但不能,实际上并不能解决任何问题。除非您有充分的理由不这样做,否则Task应该await-ed 返回函数。在这里没有理由为什么放弃任务会比使用async void方法已经接受的答案更好。

我也更喜欢void方法。这使StackOverflow比Microsoft更加智能,但这当然是必须的。
13年

4
async void引入了有关错误处理的严重问题,并导致无法测试的代码(请参阅我的MSDN文章)。最好使用该变量-如果您完全确定要以静默方式吞下异常。运算符更有可能希望从2开始Task,然后执行await Task.WhenAll
斯蒂芬·克莱里

@StephenCleary是否可以忽略未观察任务的异常。如果您的代码有可能被用于其他人的项目中,请不要让这种情况发生。async void DoNotWait(Task t) { await t; }可以使用一种简单的辅助方法来避免async void您描述的方法的缺点。(而且我不认为Task.WhenAllOP想要的是,但很可能是。)

@hvd:您的帮助程序方法将使其可测试,但是它仍然具有非常差的异常处理支持。
斯蒂芬·克莱里

3

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

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

问候


1

这是导致多余代码的简化示例。通常,您会希望使用程序中某个时刻从阻塞源获取的数据,因此您希望返回结果,以便可以获取数据。

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


确实想使用从阻塞源获取的数据,但是我不想在等待时阻塞调用方。如果没有异步,则可以通过传递回调来实现。在我的示例中,我有两个异步方法需要依次调用,第二个调用需要使用第一个返回的值。这将意味着嵌套回调处理程序,这会变得很丑陋。我一直在阅读的文档表明,这是专门async用于清理(例如)的内容
Mud 2013年

@Mud:只需将第一个异步调用的结果作为参数发送到第二个调用即可。这样,第二种方法中的代码立即开始,并且可以感觉到它等待第一次调用的结果。
Guffa

我可以做到,但是没有异步就意味着嵌套的回调,就像这样:MethodWithCallback((result1) => { Use(result1); MethodWithCallback((result2) => { Use(result1,result2); })即使在这个简单的例子中,解析也是一个bit子。使用异步,当我编写时,会为我生成等效的代码,result1 = await AsyncMethod(); Use(result1); result2 = await AsyncMethod(); Use(result1,result2); 这更容易阅读(尽管在此注释中,两个代码都可读性很差!)
Mud

@Mud:是的。但是您可以在第一个异步方法之后立即调用第二个异步方法,没有理由让它等待对的同步调用Use
Guffa

我真的不明白你在说什么。MethodWithCallback是异步的。如果我不等第一个电话就打回电话,第二个电话可能会在第一个电话之前结束。但是,我需要在处理程序中的第二个第一次调用的结果。因此,我必须等待第一个电话。
2013年

0

您真的要忽略结果吗?如包括忽略任何意外的异常?

如果不是这样,您可能想看一下这个问题:抛弃式方法


0

如果您不想将方法签名更改为return void(因为return void始终应为void ed),则可以使用C#7.0+ Discard这样的功能,该功能比分配给变量的功能稍好(应该删除大多数其他功能)源验证工具警告):

public static void DoStuff()
{
    _ = GetNameAsync(); // we don't need the return value (suppresses warning)
    MainWorkOfApplicationIDontWantBlocked();
}
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.