在非异步方法中使用异步的方法

14

假设我只希望在async中运行一种方法。

所以我有一个如下的async方法:

public async Task Load(){
    Task task1 = GetAsync(1);
    Task task2 = GetAsync(2);
    Task task3 = GetAsync(3);
    var data1 = await task1; // <--Freezes here when called from GetSomethingElse()
    var data2 = await task2;
    var data3 = await task3;
    ..process data..
}
然后我试图在另一个方法中将那个async方法作为任务调用,并希望它等待直到那个特定的async代码完成。问题是它没有完成。当它到达Load()中的第一个await时,它就停止加载了。调试器变空并且没有其他错误。
像这样从非async方法中调用async方法是否可行?我有一个理由不需要让这个特定任务成为async,但是Load()函数需要成为async
public void GetSomethingElse(){
    var task1 = Load().Wait();      
}

这怎么可能?


我甚至尝试改变Load()方法,使用var data = task1.Wait()等而不是await,但无论我如何尝试都没有任何区别。如果有人能帮忙,将不胜感激。


尝试使用以下代码:var data1 = await Task.Run(() => GetAsync(1)); - John Woo
http://blogs.msdn.com/b/pfxteam/archive/2012/04/13/10293638.aspx - Robert Harvey
@JohnWoo 我确实尝试过 var data1 = task1.Wait();,按照你的方式做会有什么不同吗? - Control Freak
我能看到一个区别:John的方法保证了工作线程的启动。你看了我链接的文章吗? - Robert Harvey
1
@JohnWoo 我在 GetSomethingElse() 中使用了你的建议,现在它可以正常工作了。如果你想回答这个问题,我会接受的。顺便说一下,谢谢你。 - Control Freak
@ControlFreak @JohnWoo 使用 Task.Run 在不同的线程中运行是为了避免同步上下文,这只是一个解决方法。请参考我的回答... - i3arnon
3个回答

11

你可能遇到了死锁问题。在 ASP.Net (也包括GUI环境)中使用了 SynchronizationContext,你正在使用 Wait() 阻止线程,但此时任务需要该线程完成。

你应该使用 ConfigureAwait(false) 来告诉等待器不要捕获上下文。只需要在第一个 await 上这么做就足够了,因为后面的操作都没有 SynchronizationContext 可以捕获:

public async Task Load()
{
    Task task1 = GetAsync(1);
    Task task2 = GetAsync(2);
    Task task3 = GetAsync(3);
    var data1 = await task1.ConfigureAwait(false);
    var data2 = await task2;
    var data3 = await task3;
    //..process data.
}

然而,建议始终使用 ConfigureAwait,除非你想要捕获SynchronizationContext,因此更好的标准是:

public async Task Load()
{
    Task task1 = GetAsync(1);
    Task task2 = GetAsync(2);
    Task task3 = GetAsync(3);
    var data1 = await task1.ConfigureAwait(false);
    var data2 = await task2.ConfigureAwait(false);
    var data3 = await task3.ConfigureAwait(false);
    //..process data.
}
在您的情况下,当您想要在所有任务完成后继续时,应该使用Task.WhenAll而不是单独地await每个任务:

对于您的情况,当您希望在完成所有任务后继续执行时,应该使用Task.WhenAll而不是单独地等待每个任务:< / p>

public async Task Load()
{
    await Task.WhenAll(GetAsync(1), GetAsync(2), GetAsync(3)).ConfigureAwait(false);
    // process data.
}
注意:通常不建议执行异步同步操作,因为它没有任何好处(会在整个操作期间阻塞线程),而且可能会导致死锁(例如此处)。

如果 GetAsync 返回一个变量会怎样?你是说即使使用这种方法,我仍然会遇到死锁问题吗? - Control Freak
@ControlFreak 不需要。ConfigureAwait就解决了这个问题。在这种情况下,使用Task.WhenAll更方便。 - i3arnon
@ControlFreak 我已经更新了答案,使其更加清晰明了。 - i3arnon

2
我认为你遇到了一个经典的死锁场景。请参考这篇文章,了解更多细节。当你有一个await语句时,当前的SynchronizationContextawait调用之前被存储,并在之后恢复,方法的其余部分被发布到它上面。在GUI应用程序中,只有一个与该上下文相关联的线程,因此试图在GUI线程上执行方法的其余部分,但是由于Wait()是一个阻塞调用,它会阻塞GUI线程。

尝试使用以下代码:

public async Task Load(){
    Task task1 = GetAsync(1).ConfigureAwait(false);
    Task task2 = GetAsync(2).ConfigureAwait(false);
    Task task3 = GetAsync(3).ConfigureAwait(false);

    var data1 = await task1; // <--Freezes here when called from GetSomethingElse()
    var data2 = await task2;
    var data3 = await task3;
    ..process data..
}

如果 GetAsync 内部存在任何等待,您可能还需要在那里添加 .ConfigureAwait(false)

1
您需要将您的Load函数更改如下:

您需要将您的Load函数更改如下:

public async Task Load(){
    await new TaskFactory().StartNew(() =>
    {
        Task task1 = GetAsync(1);
        Task task2 = GetAsync(2);
        Task task3 = GetAsync(3);
        var data1 = await task1; // <--Freezes here when called from GetSomethingElse()
        var data2 = await task2;
        var data3 = await task3;
        ..process data..
    });
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接