我们有一个较大的asp.net系统,并开始使用一些基础设施库中不可更改的异步方法。虽然该系统在大多数地方没有使用任务,但基础设施却只提供了异步方法。
在代码中,我们采用以下模式使用异步方法:
在经历了重负载后,我们发现我们收到了超时错误,但服务器并没有进行任何工作(低CPU)。我们发现在向服务器发出大量调用并且线程池达到最大线程数时,任务结果造成死锁,因为它会阻塞线程,直到任务完成,但由于没有线程池线程可用来运行它,任务无法运行。
最好的解决方案是将代码全部改为异步处理,但目前还不是一个选项。另外,删除Task.Run也可能有效,但风险太大,因为测试覆盖范围不足,无法确定是否会在未经测试的流程中导致新的死锁。
我尝试实现一个新的任务调度程序,它不使用线程池,而使用不同的一组线程来运行Foo任务,但内部任务仍然在默认任务调度程序上执行,而我不想将其替换。
有没有什么办法可以在不对代码进行大规模更改的情况下解决这个问题?
这是一个重现该问题的小样本应用程序,只使用10个线程而不是真实的限制。在此示例中,Foo永远不会被调用。
在代码中,我们采用以下模式使用异步方法:
Task.Run(() => Foo()).Result
。我们使用Task.Run来防止死锁,如果某处代码中没有使用ConfigureAwait(false),则会出现死锁,而且可能会被遗漏。为了将其整合到现有的同步代码库中,我们使用Task.Result。在经历了重负载后,我们发现我们收到了超时错误,但服务器并没有进行任何工作(低CPU)。我们发现在向服务器发出大量调用并且线程池达到最大线程数时,任务结果造成死锁,因为它会阻塞线程,直到任务完成,但由于没有线程池线程可用来运行它,任务无法运行。
最好的解决方案是将代码全部改为异步处理,但目前还不是一个选项。另外,删除Task.Run也可能有效,但风险太大,因为测试覆盖范围不足,无法确定是否会在未经测试的流程中导致新的死锁。
我尝试实现一个新的任务调度程序,它不使用线程池,而使用不同的一组线程来运行Foo任务,但内部任务仍然在默认任务调度程序上执行,而我不想将其替换。
有没有什么办法可以在不对代码进行大规模更改的情况下解决这个问题?
这是一个重现该问题的小样本应用程序,只使用10个线程而不是真实的限制。在此示例中,Foo永远不会被调用。
class Program
{
static void Main(string[] args)
{
ThreadPool.SetMaxThreads(10, 10);
ThreadPool.SetMinThreads(10, 10);
for (int i = 0; i < 10; i++)
{
ThreadPool.QueueUserWorkItem(CallBack);
}
Console.ReadKey();
}
private static void CallBack(object state)
{
Thread.Sleep(1000);
var result = Task.Run(() => Foo()).Result;
}
public static async Task<string> Foo()
{
await Task.Delay(100);
return "";
}
}
Callback()
更改为async
并使用await
来等待Task.Run()
。事实上,通过在Task
上同步等待Result
是一个非常糟糕的想法,因为它实际上保证了您最终会遇到死锁问题。在这里,“all the way”指的是整个代码流程都应该是异步的,而“not an option”则表示目前无法进行此更改。 - Peter Duniho