我必须解决需要我做一件非理想事情的临时情况:我必须从同步方法内部调用异步方法。
我只想在这里说,我知道自己遇到的所有问题,并且我理解不建议这样做的原因。
也就是说,我正在处理一个大型代码库,它从上到下完全同步,而且没有办法我可以重写所有内容以在合理的时间内使用异步等待。但是我确实需要重写这个代码库的一些小部分,以使用我在过去一年左右的时间里慢慢开发的新异步 API,因为它有很多新的特性,旧的代码库可以从中受益好吧,但由于遗留原因无法获得它们。由于所有这些代码不会很快消失,我遇到了一个问题。
TL;DR:无法轻易重写大型同步代码库以支持异步,但现在需要调用另一个大型代码库,它是完全异步的。
我目前正在做同步代码库中最简单的事情:将每个异步调用包装到 Task.Run
中并以同步方式等待 Result
.
这种方法的问题是,只要同步代码库在紧密循环中执行此操作,它就会变慢。我理解原因并且我有点知道我能做什么 - 我必须确保所有异步调用都在同一个线程上启动,而不是每次都从线程池中借用一个新的线程池(这就是 Task.Run
确实如此)。这种借用和返回会导致大量切换,如果做很多的话会大大减慢速度。
除了编写我自己的更愿意重用单个专用线程的调度程序之外,我有什么选择?
更新:为了更好地说明我正在处理的内容,我提供了一个我需要做的最简单的转换示例(还有更复杂的转换)。
它基本上是简单的 LINQ 查询,在后台使用自定义 LINQ 提供程序。下面没有 EF 或类似的东西。
[旧代码]
var result = (from c in syncCtx.Query("Components")
where c.Property("Id") == id
select c).SingleOrDefault();
[新代码]
var result = Task.Run(async () =>
{
Dictionary<string, object> data;
using (AuthorizationManager.Instance.AuthorizeAsInternal())
{
var uow = UnitOfWork.Current;
var source = await uow.Query("Components")
.Where("Id = @id", new { id })
.PrepareAsync();
var single = await source.SingleOrDefaultAsync();
data = single.ToDictionary();
}
return data;
}).Result;
如前所述,这是不太复杂的示例之一,它已经包含 2 个异步调用。
更新 2:我尝试删除 Task.Run
并按照建议直接在包装器异步方法的结果上调用 .Result
通过@Evk 和@Manu。不幸的是,在我的暂存环境中对此进行测试时,我很快就陷入了僵局。我仍在尝试了解到底发生了什么,但很明显 Task.Run
在我的情况下不能简单地删除。还有其他并发症需要解决,首先...
最佳答案
我不认为你在正确的轨道上。将每个异步调用包装在 Task.Run
中对我来说似乎很可怕,它总是会启动您不需要的额外任务。但我知道在大型代码库中引入 async/await 可能会有问题。
我看到一个可能的解决方案:将所有异步调用提取到单独的异步方法中。这样,您的项目将从同步到异步的过渡非常好,因为您可以一个一个地更改方法,而不会影响代码的其他部分。
像这样:
private Dictionary<string, object> GetSomeData(string id)
{
var syncCtx = GetContext();
var result = (from c in syncCtx.Query("Components")
where c.Property("Id") == id
select c).SingleOrDefault();
DoSomethingSyncWithResult(result);
return result;
}
会变成这样:
private Dictionary<string, object> GetSomeData(string id)
{
var result = FetchComponentAsync(id).Result;
DoSomethingSyncWithResult(result);
return result;
}
private async Task<Dictionary<string, object>> FetchComponentAsync(int id)
{
using (AuthorizationManager.Instance.AuthorizeAsInternal())
{
var uow = UnitOfWork.Current;
var source = await uow.Query("Components")
.Where("Id = @id", new { id })
.PrepareAsync();
var single = await source.SingleOrDefaultAsync();
return single.ToDictionary();
}
}
由于您处于 Asp.Net 环境中,将同步与异步混合使用是一个非常糟糕的主意。我很惊讶您的 Task.Run
解决方案适合您。将新的异步代码库合并到旧的同步代码库中的次数越多,遇到的问题就越多,并且没有简单的解决方法,除非以异步方式重写所有内容。
我强烈建议您不要将异步部分混合到同步代码库中。相反,从“下到上”工作,将所有内容从同步更改为需要等待异步调用的异步。这看起来工作量很大,但与您现在搜索一些“hacks”而不修复下划线问题相比, yield 要高得多。
关于c# - Task.Run 使用自定义线程池,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47014241/