想象一下,这是一个执行数据库查询并返回结果的方法,如果为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();
}
这样,死锁不再发生,并且相对于同步版本,执行时间大大改善。
我不知道这是否是执行并行查询的规范方式。但它有效,我没有看到代码中的特殊缺陷。
答案 0 :(得分:1)
首先,关于:
当我们使用所以我会使用async / await在一个单独的线程中执行这个过程。
async
和await
时,There is no new thread created
其次:
为什么我可以在期望任务时返回ResultObj?
作为返回类型的方法的Task<TResult>
告诉它返回类型为TResult
的任务,但是我们需要返回类型为TResult
的对象,因此可以等待该方法当使用Task<TResult>
作为reutrn类型时,我们应该使用async
和await
来完成工作。
最后:
此代码可预测导致死锁
您正在使用带有方法签名的async关键字,并等待在方法中执行的下一个异步方法调用。显然,如果您使用的方法GetSomeResultAsync
实际上是异步方法并且已正确实现,那么您发布的第一个示例中的代码看起来应该不会死锁。
我建议在进入之前研究更多关于异步等待的内容,以下是一篇很好的文章: