163

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

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

我在这里使用 async 有什么问题吗?

4

10 回答 10

117

如果您真的不需要结果,您可以简单地将GetNameAsync' 签名更改为 return void

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

考虑查看相关问题的答案: 返回 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;
}

并按如下方式使用它:

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;
}
于 2013-02-15T22:03:29.217 回答
98

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

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

来自"4014"此 MSDN 页面:Compiler Warning (level 1) CS4014

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

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

C# 7.0 更新

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

_ = SomeMethodAsync();
于 2013-10-17T18:44:43.770 回答
50

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

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

扩展方法:

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

用法:

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

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

于 2015-03-28T15:42:43.657 回答
30

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; });
于 2013-08-21T01:21:40.380 回答
18

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

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/

于 2013-06-20T22:11:40.137 回答
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);

请注意,这样做会导致 ReSharper 中出现“从不使用局部变量”消息。

于 2013-05-29T21:45:09.843 回答
4

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

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

问候

于 2017-02-11T18:59:06.437 回答
1

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

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

于 2013-02-15T22:00:05.627 回答
1

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

public static void DoStuff()
{
    _ = GetNameAsync(); // we don't need the return value (suppresses warning)
    MainWorkOfApplicationIDontWantBlocked();
}
于 2019-09-21T06:20:17.483 回答
0

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

如果不是,您可能想看看这个问题:Fire and Forget approach

于 2015-08-27T15:47:33.830 回答