【问题标题】:Handle result returned by async call within async method处理异步方法中异步调用返回的结果
【发布时间】:2018-09-01 12:56:07
【问题描述】:

假设这是一个执行数据库查询并返回结果的方法,如果为 null,则将其替换为默认值 (Null object pattern)。

public ResultObj Get()
{
    var result = dbContext.GetSomeResult();

    return result ?? ResultObj.NullValue;
}

想象一下这个数据库查询是一个长时间运行的过程,所以我会使用 async/await 在单独的线程中执行这个过程。假设dbContext.GetSomeResultAsync()方法可用。

如何将此方法转换为异步方法,以便我可以编写类似的东西?

var resultTask = GetAsync();
var otherResultTask = GetSomethingElseAsync();

Task.WaitAll(resultTask, otherResultTask);

var myResult = resultTask.Result;
var myOtherResult = otherResultTask.Result;

我尝试了这个解决方案。

public async Task<ResultObj> GetAsync()
{
    var result = await dbContext.GetSomeResultAsync();

    return result ?? ResultObj.NullValue;
}

首先,我想知道为什么这段代码会编译:为什么我可以在预期 Task&lt;ResultObj&gt; 时返回 ResultObj

其次,这段代码可以预见地导致死锁,正如大量关于异步死锁反模式的资源所清楚地解释的那样。在异步调用之后使用.ConfigureAwait(false) 方法可以防止死锁。这是正确的方法吗?在这种情况下是否有任何隐藏的缺点?这是一般规则吗?

我也试过了。

public async Task<ResultObj> GetAsync()
{
    return await Task.Run(() => {
        var result = dbContext.GetSomeResult();

        return result ?? ResultObj.NullValue;
    });
}

这也会导致死锁。这次我什至无法弄清楚为什么。

编辑:可能的解决方案

最后,在阅读了this 之后,我找到了解决问题的方法。

我的通用查询包装器方法是这样的。

public async Task<ResultObj> GetAsync()
{
    var result = await dbContext.GetSomeResultAsync();

    return result ?? ResultObj.NullValue;
}

在调用方法上,我使用这种模式。

public async Task<CollectedResults> CollectAsync()
{
    var resultTask = GetAsync();
    var otherResultTask = GetSomethingElseAsync();

    //here both queries are being executed.
    //...in the while, optionally, here some other synchronous actions

    //then, await results
    var result = await resultTask;
    var otherResult = await otherResultTask;

    //here process collected results and return

    return new CollectedResults(...);
}

值得一提的是,上面的代码,包装在一个领域类中,是由一个控制器动作调用的。为了让它工作,我必须让方法一直异步,直到控制器动作,现在如下所示。

public async Task<CollectedResults> Get()
{
    return await resultsCollector.CollectAsync();
}

这样,死锁不会再发生,并且相对于同步版本而言,执行时间大大缩短。

我不知道这是否是执行并行查询的规范方式。但它有效,我没有在代码中看到特别的缺陷。

【问题讨论】:

  • 因为 async/await 而编译。这是关键字的一个特征。如果您返回 Task&lt;ResultObj&gt; ,您会收到错误消息。你能告诉我为什么这会导致死锁吗?
  • 另外,async/await 并不意味着不同的线程。这意味着当您等待来自 Db 的结果时,而不是阻塞线程,您可以让线程执行其他操作并在结果可用时返回它。
  • 谢谢。实际上,我可以毫无错误地返回Task&lt;ResultObj&gt;。死锁是因为这样的事情发生的:blog.stephencleary.com/2012/07/dont-block-on-async-code.html

标签: c# async-await


【解决方案1】:

首先,关于:

所以我会使用 async/await 在单独的线程中执行这个过程。

There is no new thread created 当我们使用asyncawait

其次:

为什么我可以在预期任务时返回 ResultObj?

Task&lt;TResult&gt; 作为方法的返回类型告诉它返回一个TResult 类型的任务,但我们需要从它返回TResult 类型的对象,以便可以等待该方法以及在使用Task&lt;TResult&gt; 时作为 reutrn 类型,我们应该使用 asyncawait 来完成这项工作。

最后:

这段代码会导致死锁

您正在使用带有方法签名的 async 关键字,并且还在等待从方法内完成的下一个异步方法调用。因此,如果您使用的方法 GetSomeResultAsync 确实是异步方法并且已正确实现,那么显然您发布的第一个示例中的代码不应该死锁。

我建议在深入了解异步等待之前先研究一下,以下是一篇不错的文章:

https://blog.stephencleary.com/2012/02/async-and-await.html

【讨论】:

  • 感谢您提供有趣的链接。我的 async 方法当然实现得很好,因为它属于一个公共库。到目前为止,还没有解决我的问题。如果我直接返回 Task&lt;ResultObj&gt; 而不在异步方法中检查 null (然后每次在调用后检查外部),我没有问题。
猜你喜欢
  • 2019-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-30
  • 1970-01-01
  • 1970-01-01
  • 2016-06-21
  • 1970-01-01
相关资源
最近更新 更多