Task.Run使用自定义线程池

时间:2017-10-30 11:33:11

标签: c# async-await

我必须解决一个临时情况,要求我做一个非理想的事情:我必须从同步内部调用异步方法。

我现在要说的是,我知道我所遇到的所有问题,并且我理解为什么不建议这样做。

那就是说,我正在处理一个大型代码库,它从上到下完全同步,并且没办法我可以在合理的时间内重写所有内容以使用异步等待。但我确实需要重写这个代码库的一些小部分来使用我在过去一年左右慢慢开发的新异步API,因为它有很多新功能,旧代码库可以从中受益好吧,但由于遗留原因无法得到它们。由于所有代码都不会很快消失,我面临着一个问题。

TL; DR:无法轻松重写大型同步代码库以支持异步,但现在需要调用另一个完全异步的大型代码库。

我目前正在执行同步代码库中最简单的操作:将每个异步调用包装到Task.Run并以同步方式等待Result

这种方法的问题是,只要同步代码库在紧密循环中执行此操作,它就会变慢。我理解原因并且我知道我能做什么 - 我必须确保所有异步调用都是在同一个线程上启动而不是每次从线程池中借用一个新的异步调用(这是{{1确实)。这种借用和返回会导致很多切换,如果做了很多的话,可能会大大减慢速度。

我有什么选择,除了编写我自己的调度程序之外,还希望重用一个专用线程?

UPDATE :为了更好地说明我正在处理的内容,我提供了一个我需要做的最简单转换的例子(还有更复杂的转换)。

它基本上是简单的LINQ查询,它使用了自定义LINQ提供程序。下面没有EF或类似的东西。

[旧代码]

Task.Run

[新密码]

var result = (from c in syncCtx.Query("Components")
    where c.Property("Id") == id
    select c).SingleOrDefault();

如上所述,这是一个不那么复杂的例子,它已经包含2个异步调用。

更新2 :我尝试删除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; 并直接在包装异步方法的结果上调用Task.Run,如@Evk和@Manu所示。不幸的是,在我的暂存环境中进行测试时,我很快遇到了僵局。我仍然试图了解到底发生了什么,但显而易见的是.Result在我的情况下不能简单地删除。还有其他要解决的问题,首先......

1 个答案:

答案 0 :(得分:2)

我认为你没有走上正轨。在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解决方案适合您。将新的异步代码库合并到旧的同步代码库中的次数越多,遇到的问题就越多,除了以异步方式重写所有内容之外,没有简单的解决办法。

我强烈建议您不要将异步部分混合到同步代码库中。相反,工作从“从下到上”,将所有内容从同步更改为异步,您需要等待异步调用。这可能看起来很多工作,但是好处远远高于现在搜索某些“黑客”并且不解决下划线问题的好处。