【问题标题】:Asynchronous task makes program hang异步任务使程序挂起
【发布时间】:2020-04-21 12:49:11
【问题描述】:

我有一个任务需要向服务器调用 http 请求,我这样做:

public static async Task<BoundingBox> Transform(this BoundingBox boundingBox, string epsg) {
    ...
    var min = _httpClient.GetStringAsync("https://epsg.io/trans?x=435951&y=5549182&s_srs=25832&t_srs=3857");
    var max =  _httpClient.GetStringAsync("https://epsg.io/trans?x=435911&y=5549122&s_srs=25832&t_srs=3857");
    await Task.WhenAll(min, max);
    ...
}
priorityBb = bb.Transform("epsg:3857").GetAwaiter().GetResult();

但这会让我的 UI 挂起。

我的代码有什么问题?非常感谢您的评论。

【问题讨论】:

  • 为什么要在函数内部正确使用await,然后在调用函数时使用throw it all away
  • @GSerg 他仍然能够在 TaskWhenAll 之后获得min.Result
  • 这个priorityBb = bb.Transform("epsg:3857").GetAwaiter().GetResult(); 看起来不太好。 GetResult(); 阻塞当前线程。使用priorityBb = await bb.Transform("epsg:3857");.. 并且您需要在某处收集结果以从转换中返回它们。
  • @JeroenvanLangen 但是Transform 将在Task.WhenAll 返回之前将不完整的异步任务返回给调用者。这就是为什么它能够deadlock
  • @JeroenvanLangen 我明白了。您将我的"throw it all away" 理解为丢弃函数调用结果的参考。不,我说的是要抛弃异步机制和好处。

标签: c# asynchronous async-await


【解决方案1】:

您必须等待Transform 方法,因为返回的任务可能尚未完成而不是GetAwaiter().GetResult()。您可能永远不需要使用这些方法。 GetResult() 会在任务未完成时阻塞当前线程。

这是一个粗略的草图,我对你的结构的信息太少了:

public class MyResults
{
    public string Min {get;set;}
    public string Max {get;set;}
}

public static async Task<MyResults> Transform(this BoundingBox boundingBox, string epsg) {
    ...
    var minTask = _httpClient.GetStringAsync("https://epsg.io/trans?x=435951&y=5549182&s_srs=25832&t_srs=3857");
    var maxTask =  _httpClient.GetStringAsync("https://epsg.io/trans?x=435911&y=5549122&s_srs=25832&t_srs=3857");

    await Task.WhenAll(minTask, maxTask);

    // you can access the Results now, because all tasks are completed.
    return new MyResults { Min = minTask.Result, Max = minTask.Result };
}


public static async Task GetMyData()
{
    var myResults = await bb.Transform(".....");
    //              ^^^^^

    Console.WriteLine(myResults.Min);
    Console.WriteLine(myResults.Max);
}

如果调用者不支持异步,你可以尝试类似的方法:(还没有测试过,你必须检查一下)

TaskScheduler.FromCurrentSynchronizationContext() 仅在处理(例如)UI 线程时才需要。

public static void GetMyData()
{
    // You are not able to await it here. Fire and "forget"

    Task.Run<MyResults>(() =>
    {
        // Not executed on the UI thread
        return bb.Transform(".....");

    })
    .ContinueWith(transformTask =>
    {
        // back on the UI thread.....
        var myResults = transformTask.Result;

        Console.WriteLine(myResults.Min);
        Console.WriteLine(myResults.Max);

    }, TaskScheduler.FromCurrentSynchronizationContext());
}

【讨论】:

  • 非常感谢您的评论。我在ViewModel 的执行命令中调用await bb.Transform("....."); 这个ViewModel 扩展自ScreenCaliburn.Micro
  • 所以如果await bb.Transform(".....");的调用方法不能设置异步,我该如何处理
【解决方案2】:
priorityBb = bb.Transform("epsg:3857").GetAwaiter().GetResult();

由于调用GetResult,此行阻塞了您的 UI 线程。之前您正确使用了async/await,但是在这一行中,您将异步代码与阻塞代码混合在一起。您应该使用与Transform 方法和await 结果相同的方法,而不是使用GetResult 阻塞

要解决这个问题,只需将该行更改为

priorityBb = await bb.Transform("epsg:3857");

像这样使用GetResult 会导致死锁,并且在大多数情况下不是一个好主意。只要你坚持async/await即可。

如果您不能拨打电话async,那么您的电话就不能拨打asyncasync/await 可能是这里最好的方法,但如果由于某种原因你不能使用它,你可以等待响应并使用 Task.Run 在另一个线程上处理它

【讨论】:

    猜你喜欢
    • 2018-06-27
    • 2017-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多