【问题标题】:Combine Results from Async Method and Return Synchronously合并异步方法的结果并同步返回
【发布时间】:2015-05-25 22:41:39
【问题描述】:

我读过很多帖子,人们都遇到过类似的问题,但他们似乎做出了不适用的假设,或者他们的代码根本不适合我。我需要结合异步的结果方法。 我想要的唯一异步是结果的组合。由于 Azure 服务总线一次只允许我抓取 256 条消息,我想发送多个请求以获取几批一次,并使其成为一个列表。

似乎有一个假设,如果您正在调用异步 工作完成时要返回给调用者的方法 (即:一些长期运行的任务)。但是,我根本不想要那个。我 想等待任务完成然后拿我的组合清单 并返回它。

首先我不想用异步标记我的调用方法。我可以,但我为什么要这样做,我无论如何都要调用一个同步方法,该方法恰好在返回给我之前执行一些异步操作。

我已经看到使用WhenAll() 然后使用结果的示例,但这对我不起作用。我已经尝试了所有不同的排列,但它要么锁定了我的应用程序,要么告诉我任务尚未执行。

这是我目前拥有的:

public IEnumerable<BrokeredMessage>[] GetCombinedResults()
{
            var job = () => ServiceBus.TrackerClient.ReceiveBatchAsync(BatchLimit);
            Task<IEnumerable<BrokeredMessage>> task1 = _retryPolicy.ExecuteAsync<IEnumerable<BrokeredMessage>>(job);
            Task<IEnumerable<BrokeredMessage>> task2 = _retryPolicy.ExecuteAsync<IEnumerable<BrokeredMessage>>(job);
            IEnumerable<BrokeredMessage>[] results = Task.WhenAll<IEnumerable<BrokeredMessage>>(task1, task2).Result;
            return results;
}

但是调用结果会导致它被锁定。我已经阅读过这样做可能会发生死锁,但在其他问题中已经看到了这个建议作为答案。 如果我调用 Task.WaitAll() 并且不关心结果,则这种类型的设置可以正常工作。 不知道为什么当我想要任务返回的结果时这会变得困难。我尝试过使用 Task.Run,​​但它在得到结果之前就退出了我的方法。

【问题讨论】:

    标签: c# azure asynchronous async-await


    【解决方案1】:

    您似乎对使用异步进行扇出并行处理感兴趣。这是完全有效的做法。没有必要使整个调用链异步来利用扇出并行性。

    您偶然发现了常见的 ASP.NET 死锁。您可以使用Task.Run 作为一种简单、安全的方式来避免它。首先,让GetCombinedResults 异步以保持简单和一致:

    public async Task<IEnumerable<BrokeredMessage>[]> GetCombinedResultsAsync()
    {
                var job = () => ServiceBus.TrackerClient.ReceiveBatchAsync(BatchLimit);
                Task<IEnumerable<BrokeredMessage>> task1 = _retryPolicy.ExecuteAsync<IEnumerable<BrokeredMessage>>(job);
                Task<IEnumerable<BrokeredMessage>> task2 = _retryPolicy.ExecuteAsync<IEnumerable<BrokeredMessage>>(job);
                IEnumerable<BrokeredMessage>[] results = await Task.WhenAll<IEnumerable<BrokeredMessage>>(task1, task2);
                return results;
    }
    

    这个方法显然是正确的。它不会混合同步和异步。这么称呼它:

    var results = Task.Run(() => GetCombinedResultsAsync()).Result;
    

    之所以可行,是因为GetCombinedResultsAsync 现在在没有同步上下文的情况下执行。

    【讨论】:

    • 谢谢!我仍然掌握异步/等待的东西。我仍然不确定为什么 WaitAll() 如此简单。我不需要 await 关键字,也不需要将当前方法标记为异步。我只是创建一些任务并说 WaitAll()。如果我想要任务突然完成后的结果,我必须将当前方法标记为异步。看起来很奇怪。
    • 我不知道为什么 WaitAll 会起作用。它不应该。而且 Task.Run 事情肯定应该工作。使用调试器找出挂起的代码段。或者,注释掉 Task.Run 行,看看它是否有效。有一些您尚未发布的相关代码。发布更多代码。
    • @KingOfHypocrites:WaitAll() 阻止当前线程等待所有任务完成,因此根据您的同步上下文(UI 和 Asp.NET)可能会发生死锁。 usr 的解决方案在执行异步方法时删除了同步上下文。请参阅 thisthis 以获得有关同步上下文的良好解释。
    • @KingOfHypocrites 我不确定为什么 Task.WaitAll() 在您的情况下不会阻塞(请参阅answer)。这是另一个link,它展示了执行“同步异步”时的最佳实践
    • @PauloMorgado 使用 Task.Run 这不会死锁,因为没有人再发布到同步上下文。 lambda 主体不知道任何同步上下文。等待和阻塞本身不是问题。这不是死锁的充分条件。出于同样的原因,您可以安全地调用阻塞 API,例如 Thread.Sleep 而不会出现死锁或遭受任何其他惩罚。我们在 ASP.NET 中使用阻塞调用已有 15 年了,它们仍然有效。
    猜你喜欢
    • 2016-05-14
    • 1970-01-01
    • 1970-01-01
    • 2018-09-01
    • 2017-06-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-22
    • 1970-01-01
    相关资源
    最近更新 更多