c# - Task.Run 使用自定义线程池

标签 c# async-await

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

我只想在这里说,我知道自己遇到的所有问题,并且我理解不建议这样做的原因。

也就是说,我正在处理一个大型代码库,它从上到下完全同步,而且没有办法我可以重写所有内容以在合理的时间内使用异步等待。但是我确实需要重写这个代码库的一些小部分,以使用我在过去一年左右的时间里慢慢开发的新异步 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/

相关文章:

c# - 我如何测试以查看网络管道服务是否正在监听

c# - 确定何时将文件复制到 Windows 7 中的目录

javascript - 为什么 await/async 的结果未定义?

c# - 当有数百个 Task.Delay 时性能如何

flutter - 如何从异步/等待函数返回 bool 值并将其传递给其他页面中的其他变量

c# - 部署到 "C:\Program Files\..."后,我的应用程序无法更新 Access 数据库

c# - 使用 LINQ to SQL 的 Web 应用程序中非常慢的问题

c# - Blazor - 私有(private)组件

c# - 运行异步 lambda 时,使用 Task.Run(func) 还是 new Func<Task>(func)()?

javascript - API调用期间如何在reducer函数中返回状态