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

Mud*_*Mud 121 c# async-await

刚刚获得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();
    });
}
Run Code Online (Sandbox Code Playgroud)

这是一个调用它的示例方法.如果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);
}
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.问题是当我调用 GetNameAsync时:

public static void DoStuff()
{
    GetNameAsync();
    MainWorkOfApplicationIDontWantBlocked();
}
Run Code Online (Sandbox Code Playgroud)

重点GetNameAsync是它是异步的.我不希望它阻止,因为我想回到MainWorkOfApplicationIDontWantBlocked ASAP并让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.
Run Code Online (Sandbox Code Playgroud)

我完全清楚"在完成调用之前,当前方法的执行仍在继续".这是该的异步代码,对不对?

我更喜欢我的代码在没有警告的情况下进行编译,但是这里没有什么可以"修复"的,因为代码正在完成我打算做的事情.我可以通过存储返回值来摆脱警告GetNameAsync:

public static void DoStuff()
{
    var result = GetNameAsync(); // supress warning
    MainWorkOfApplicationIDontWantBlocked();
}
Run Code Online (Sandbox Code Playgroud)

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

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

    public static Task GetNameWrapper()
    {
        return GetNameAsync();
    }
Run Code Online (Sandbox Code Playgroud)

但那是更多的代码.所以我必须编写我不需要的代码或容忍不必要的警告.

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

Nik*_*hil 89

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

public static async void GetNameAsync()
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

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

更新

如果您需要结果,可以更改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;
}
Run Code Online (Sandbox Code Playgroud)

并使用如下:

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;
}
Run Code Online (Sandbox Code Playgroud)

  • 作为一般规则,除了事件处理程序之外,你永远不应该有一个`async void`方法. (29认同)
  • 只有当他*从不*关心结果时才会出现这种情况,而不是只需要*这一次*. (14认同)
  • @Servy,对,谢谢你的澄清.但OP的`GetNameAsync`不返回任何值(当然除了结果本身). (3认同)
  • 正确,但通过返回任务,他可以知道何时完成所有异步操作.如果它返回"void",他就无法知道它何时完成.当我在之前的评论中说"结果"时,这就是我的意思. (2认同)
  • 这里需要注意的另一件事是,当你切换到`async void`时,行为是不同的.你没有捕获的任何异常都会导致你的进程崩溃,但在.net 4.5中它会继续运行. (2认同)

Wil*_*lem 48

我讨论的时间已经很晚了,但也可以选择使用#pragma预处理器指令.我在这里和那里有一些异步代码我明确地不想在某些条件下等待,我不喜欢警告和未使用的变量就像其他人一样:

#pragma warning disable 4014
SomeMethodAsync();
#pragma warning restore 4014
Run Code Online (Sandbox Code Playgroud)

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

另请参阅@ ryan-horath的警告/答案/sf/answers/850153321/.

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

  • 甚至不需要说 `var`,只需写 `_ = SomeMethodAsync();` (4认同)
  • 太棒了 我不知道你能做到这一点。谢谢! (2认同)

Joe*_*age 38

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

我的解决方案是创建一个Task的扩展方法,称为DoNotAwait(),它什么都不做.这不仅会抑制所有警告,ReSharper或其他方式,而且会使代码更容易理解,并向未来的维护者表明您真正打算等待调用.

扩展方法:

public static class TaskExtensions
{
    public static void DoNotAwait(this Task task) { }
}
Run Code Online (Sandbox Code Playgroud)

用法:

public static void DoStuff()
{
    GetNameAsync().DoNotAwait();
    MainWorkOfApplicationIDontWantBlocked();
}
Run Code Online (Sandbox Code Playgroud)

编辑添加:这类似于Jonathan Allen的解决方案,如果尚未启动扩展方法将启动任务,但我更喜欢具有单一用途的功能,以便调用者的意图完全清楚.

  • 我喜欢这个,但我将它重命名为 Unawait() ;) (2认同)

小智 27

async void很糟糕!

返回void和返回任务有什么区别? http://www.jaylee.org/post/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.aspx http://www.tonicodes.net/blog /为什么任您应该-几乎从不写空隙异步方法/

我建议您通过匿名方法明确运行任务...

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

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

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

但是,如果你的"GetNameAsync"方法必须与UI甚至任何UI绑定进行交互,(WINRT/MVVM,我正在看着你); 然后它有点funkier =)

您需要将引用传递给UI调度程序,如下所示...

Task.Run(async () => await GetNameAsync(CoreApplication.MainView.CoreWindow.Dispatcher));
Run Code Online (Sandbox Code Playgroud)

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

dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {  this.UserName = userName; });
Run Code Online (Sandbox Code Playgroud)

  • 你提到的第一种方法会引发一个不同的警告:`这种异步方法缺少'等待'运算符并且将同步运行.考虑使用'await'运算符来等待非阻塞API调用,或'await Task.Run(...)'在后台线程上执行CPU绑定工作.这也会导致创建新线程,而不一定只使用async/await创建新线程. (8认同)

Jon*_*len 16

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

SomeAyncFunction().RunConcurrently();
Run Code Online (Sandbox Code Playgroud)

在哪里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(); 
 } 
Run Code Online (Sandbox Code Playgroud)

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

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

  • 你只需要这个:`public static void忘了(这个Task任务){}` (3认同)

dev*_*ord 7

根据微软关于此警告的文章,您可以通过简单地将返回的任务分配给变量来解决它.下面是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);
Run Code Online (Sandbox Code Playgroud)

请注意,执行此操作将导致ReSharper中的"Local variable is never used"消息.

  • `async void`引入了严重的错误处理问题,导致无法测试的代码(参见[我的MSDN文章](http://msdn.microsoft.com/en-us/magazine/jj991977.aspx)).使用变量会好得多 - 如果你绝对确定*你想要静默吞下异常.更有可能的是,op会想要启动两个`Task`s然后执行`await Task.WhenAll`. (4认同)