C# 有人能解释这种死锁行为吗

C# 有人能解释这种死锁行为吗,c#,asp.net,asynchronous,concurrency,C#,Asp.net,Asynchronous,Concurrency,TLDR:在我下面的示例(ASP.NET)中,为什么task1.Result和task2.Result会导致死锁,而task3.Result不会 我们的代码中有一个类,在这个类中,我无法轻松地将该方法标记为异步,并在等待异步任务完成时防止代码阻塞。原因是我们的框架不支持这一点。因此,我试图用task.Result找到解决方案,但遇到了一些死锁。幸运的是,我找到了一个解决方案(请参见任务3)。现在我试图找出task1、task2和task3之间的区别,以理解为什么前两个会导致死锁,而第三个不会 使

TLDR:在我下面的示例(ASP.NET)中,为什么task1.Result和task2.Result会导致死锁,而task3.Result不会

我们的代码中有一个类,在这个类中,我无法轻松地将该方法标记为异步,并在等待异步任务完成时防止代码阻塞。原因是我们的框架不支持这一点。因此,我试图用task.Result找到解决方案,但遇到了一些死锁。幸运的是,我找到了一个解决方案(请参见任务3)。现在我试图找出task1、task2和task3之间的区别,以理解为什么前两个会导致死锁,而第三个不会

使用调试器时,我没有看到任何与task3在调用task3.Result之前运行task3类似的差异。它仍然像其他两个一样处于等待激活状态。有人能向我解释一下task3是如何工作的吗

公共类HomeController:控制器
{
公共行动结果GetSomething()
{
var task1=GetSomethingAsync();
var task2=Task.Run(异步()=>wait task1);
var task3=Task.Run(async()=>await-GetSomethingAsync());
返回内容(task1.Result);
//返回内容(任务2.结果);
//返回内容(任务3.结果);
}
私有静态异步任务GetSomethingAsync()
{
返回等待任务。运行(()=>“某物”);
}
}

什么是
GetSomethingAsync()
它是由调用线程完成的,直到某个操作(OP)无法立即完成(例如io),然后控制流被提供给调用函数,但是。 如果随后访问返回的
任务
对象上的
结果
属性,线程将被阻塞

这导致了一个问题,即即使OP已经完成,线程也不会知道,因为他正忙于等待任务的完成

但是,如果让某个线程池线程执行
GetSomethingAsync()
(它是
Task.Run(…)
执行的),则线程池线程可以完成操作,并且可以通知调用线程任务已完成

更新: 第二种方法不起作用,因为任务仍在主线程上启动。如果你有这个方法

公共静态异步任务DoStuffAsync()
{
WriteLine($“在线程{thread.CurrentThread.ManagedThreadId}上执行某些工作1”);
等待任务。延迟(50);
WriteLine($“在线程{thread.CurrentThread.ManagedThreadId}上执行一些工作2”);
}
并在具有
SynchronizationContext

var task=DoStuffAsync();
WriteLine($“在线程{thread.CurrentThread.ManagedThreadId}上做主要工作”);
wait Task.Run(异步()=>wait Task);
它将输出如下内容:

Doing some stuff1 on thread 1
Doing main stuff on thread 1
Doing some stuff2 on thread 1

因此,在代码行
Task.Run(async()=>await Task)
中,您只实现了线程池线程在原始
任务完成时等待,但这反过来又会创建一个新的
任务
,如果不等待它就会导致死锁。

是否由调用线程完成,直到某些操作(OP)无法立即完成(例如io),然后将控制流提供给调用函数,但是。 如果随后访问返回的
任务
对象上的
结果
属性,线程将被阻塞

这导致了一个问题,即即使OP已经完成,线程也不会知道,因为他正忙于等待任务的完成

但是,如果让某个线程池线程执行
GetSomethingAsync()
(它是
Task.Run(…)
执行的),则线程池线程可以完成操作,并且可以通知调用线程任务已完成

更新: 第二种方法不起作用,因为任务仍在主线程上启动。如果你有这个方法

公共静态异步任务DoStuffAsync()
{
WriteLine($“在线程{thread.CurrentThread.ManagedThreadId}上执行某些工作1”);
等待任务。延迟(50);
WriteLine($“在线程{thread.CurrentThread.ManagedThreadId}上执行一些工作2”);
}
并在具有
SynchronizationContext

var task=DoStuffAsync();
WriteLine($“在线程{thread.CurrentThread.ManagedThreadId}上做主要工作”);
wait Task.Run(异步()=>wait Task);
它将输出如下内容:

Doing some stuff1 on thread 1
Doing main stuff on thread 1
Doing some stuff2 on thread 1

因此,在代码行
Task.Run(async()=>await Task)
中,您只实现了线程池线程在原始
任务完成时等待,但这反过来又会创建一个新的
任务,如果不通过等待来处理它,就会导致死锁。

问题的根源是
await
,这里:

private static async Task<string> GetSomethingAsync()
{
    return await Task.Run(() => "something");
}
private静态异步任务GetSomethingAsync()
{
返回等待任务。运行(()=>“某物”);
}
在进入等待线程之前捕获上下文,上下文是主线程。因此,在等待之后,必须恢复上下文,因此将继续安排在主线程中运行。由于主线程在这一点上被阻塞,它无法处理延续,因此出现死锁

为了防止捕获上下文,您可以使用配置此
wait


Update:我所说的continuation是指等待之后的
getsomethingsync
中的代码。尽管之后没有代码,但编译器似乎确实费心为方法的这个无操作部分创建一个实际的延续(否则在您的示例中不应该出现死锁)

应该注意的是,编译器将
async
方法转换为包含多个