等待无效的异步方法


155

我如何等待一种void async方法完成工作?

例如,我有如下功能:

async void LoadBlahBlah()
{
    await blah();
    ...
}

现在,我想确保所有内容都已加载,然后再继续其他操作。

Answers:


234

最佳做法是async void仅在触发并忘记方法时才标记功能,如果要等待,则应将其标记为async Task

如果您仍然想等待,请像这样包装 await Task.Run(() => blah())


blah很明显正在返回任务,并且具有异步签名,为什么您不等待就调用它。甚至VS也会警告您。
batmaci

8
await Task.Run(() => An_async_void_method_I_can_not_modify_now())
themefield

1
await Task.Run(() => blah())有误导性。这并不等待异步功能的完成blah,它只是等待任务的(平凡的)创建,并在blah()完成之前立即继续。
乔纳森·利德贝克

@JonathanLidbeck语句“在错误之前立即继续。尝试“等待Task.Run(()=> Thread.Sleep(10_000))”),等待10秒以上,然后再执行下一行
Rohit Sharma

@RohitSharma,这对于您的示例是正确的,但Thread.Sleep不是异步的。这个问题是关于等待async void功能的,例如async void blah() { Task.Delay(10000); }
Jonathan Lidbeck

59

如果可以将函数的签名更改为,async Task则可以使用此处提供的代码


27

最好的解决方案是使用async Task。您应避免async void出于多种原因,其中之一是可组合性。

如果无法使该方法返回Task(例如,它是一个事件处理程序),则可以SemaphoreSlim在该方法即将退出时使用方法信号。考虑分步进行finally


1

执行一个AutoResetEvent,调用该函数,然后等待AutoResetEvent,然后在知道完成后将其设置在异步void内。

您还可以等待从无效异步返回的任务


1

您实际上不需要手动执行任何操作,await关键字会暂停函数执行,直到blah()返回为止。

private async void SomeFunction()
{
     var x = await LoadBlahBlah(); <- Function is not paused
     //rest of the code get's executed even if LoadBlahBlah() is still executing
}

private async Task<T> LoadBlahBlah()
{
     await DoStuff();  <- function is paused
     await DoMoreStuff();
}

T是对象blah()返回的类型

您不能真正await实现void功能,因此LoadBlahBlah()不能void


我想等待LoadBlahBlah()完成,而不是等待blah()
MBZ 2012年

10
不幸的是,异步void方法不会暂停执行(不同于异步Task方法)
ghord 2013年

-1

我知道这是一个古老的问题,但这仍然是我一直遇到的问题,但是在异步void签名方法中使用async / await时,仍然没有明确的解决方案可以正确地做到这一点。

但是,我注意到.Wait()在void方法中可以正常工作。

并且由于异步void和void具有相同的签名,因此您可能需要执行以下操作。

void LoadBlahBlah()
{
    blah().Wait(); //this blocks
}

令人困惑的是,异步/等待不会阻塞下一个代码。

async void LoadBlahBlah()
{
    await blah(); //this does not block
}

当您反编译代码时,我的猜测是异步void会创建一个内部Task(就像异步Task一样),但是由于签名不支持返回该内部Task

这意味着内部异步void方法仍将能够“等待”内部异步方法。但外部无法知道内部任务何时完成。

因此,我的结论是异步void可以按预期工作,如果需要内部Task的反馈,则需要使用异步Task签名。

希望我的漫步对任何也在寻找答案的人都有意义。

编辑:我做了一些示例代码,并将其反编译以查看实际情况。

static async void Test()
{
    await Task.Delay(5000);
}

static async Task TestAsync()
{
    await Task.Delay(5000);
}

变成(编辑:我知道主体代码不在这里,而是在状态机中,但是状态机基本上是相同的,因此我不必费心添加它们)

private static void Test()
{
    <Test>d__1 stateMachine = new <Test>d__1();
    stateMachine.<>t__builder = AsyncVoidMethodBuilder.Create();
    stateMachine.<>1__state = -1;
    AsyncVoidMethodBuilder <>t__builder = stateMachine.<>t__builder;
    <>t__builder.Start(ref stateMachine);
}
private static Task TestAsync()
{
    <TestAsync>d__2 stateMachine = new <TestAsync>d__2();
    stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
    stateMachine.<>1__state = -1;
    AsyncTaskMethodBuilder <>t__builder = stateMachine.<>t__builder;
    <>t__builder.Start(ref stateMachine);
    return stateMachine.<>t__builder.Task;
}

AsyncVoidMethodBuilder或AsyncTaskMethodBuilder都没有在Start方法中实际包含任何代码来暗示它们将被阻止,并且始终在它们启动后异步运行。

这意味着如果没有返回的Task,将无法检查其是否完整。

如预期的那样,它只会启动异步运行的Task,然后在代码中继续。和异步任务,首先启动任务,然后返回它。

所以我想我的答案是永远不要使用异步void,如果您需要知道任务何时完成,那就是异步Task的目的。

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.