【发布时间】: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<ResultObj> 时返回 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<ResultObj>,您会收到错误消息。你能告诉我为什么这会导致死锁吗? -
另外,async/await 并不意味着不同的线程。这意味着当您等待来自 Db 的结果时,而不是阻塞线程,您可以让线程执行其他操作并在结果可用时返回它。
-
谢谢。实际上,我可以毫无错误地返回
Task<ResultObj>。死锁是因为这样的事情发生的:blog.stephencleary.com/2012/07/dont-block-on-async-code.html
标签: c# async-await