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

时间:2013-02-15 21:49:13

标签: 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();
    });
}

这是一个调用它的示例方法。如果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 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.

我完全清楚“在完成呼叫之前,当前方法的执行仍在继续”。这是异步代码的 point ,对吗?

我更喜欢我的代码在没有警告的情况下进行编译,但是这里没有什么可以“修复”的,因为代码正在完成我打算做的事情。我可以通过存储GetNameAsync

的返回值来消除警告
public static void DoStuff()
{
    var result = GetNameAsync(); // supress warning
    MainWorkOfApplicationIDontWantBlocked();
}

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

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

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

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

我在这里使用async有什么不对吗?

10 个答案:

答案 0 :(得分:82)

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

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

考虑查看相关问题的答案: What's the difference between returning void and returning a 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;
}

答案 1 :(得分:41)

我讨论的时间已经很晚了,但也可以选择使用#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返回值分配给变量。这可以确保您可以访问抛出的任何异常,这些异常将在返回值中显示。

答案 2 :(得分:32)

我并不特别喜欢将任务分配给未使用的变量或更改方法签名以返回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的解决方案,如果尚未启动扩展方法将启动任务,但我更喜欢使用单一功能,以便调用者的意图是完全清楚。

答案 3 :(得分:26)

async void很糟糕!

  1. What's the difference between returning void and returning a Task?
  2. https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html
  3. 我建议您通过匿名方法明确运行Task ...

    e.g。

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

答案 4 :(得分: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/

答案 5 :(得分: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中的“Local variable is never used”消息。

答案 6 :(得分:2)

这是一个简单的解决方案。

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

此致

答案 7 :(得分:1)

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

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

答案 8 :(得分:0)

你真的想忽略这个结果吗?如包括忽略任何意外的例外?

如果不是,您可能需要查看此问题:Fire and Forget approach

答案 9 :(得分:0)

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

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