处理异步方法

时间:2018-03-22 22:45:26

标签: c# async-await

想象一下,这是一个执行数据库查询并返回结果的方法,如果为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;
}

首先,我想知道为什么这段代码会编译:为什么我可以在预期ResultObj时返回Task<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(...);
}

值得一提的是,包含在域类中的上述代码由Controller操作调用。为了实现这一点,我不得不一直使方法异步,直到Controller动作,现在显示如下。

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

这样,死锁不再发生,并且相对于同步版本,执行时间大大改善。

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

1 个答案:

答案 0 :(得分:1)

首先,关于:

  

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

当我们使用asyncawait

时,

There is no new thread created

其次:

  

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

作为返回类型的方法的Task<TResult>告诉它返回类型为TResult的任务,但是我们需要返回类型为TResult的对象,因此可以等待该方法当使用Task<TResult>作为reutrn类型时,我们应该使用asyncawait来完成工作。

最后:

  

此代码可预测导致死锁

您正在使用带有方法签名的async关键字,并等待在方法中执行的下一个异步方法调用。显然,如果您使用的方法GetSomeResultAsync实际上是异步方法并且已正确实现,那么您发布的第一个示例中的代码看起来应该不会死锁。

我建议在进入之前研究更多关于异步等待的内容,以下是一篇很好的文章:

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