【发布时间】:2017-01-05 02:26:30
【问题描述】:
这是一个简化的示例,我发现在某些情况下很难调试等待任务中的死锁:
class Program
{
static void Main(string[] args)
{
var task = Hang();
task.Wait();
}
static async Task Hang()
{
var tcs = new TaskCompletionSource<object>();
// do some more stuff. e.g. another await Task.FromResult(0);
await tcs.Task;
tcs.SetResult(0);
}
}
这个例子很容易理解为什么它会死锁,它正在等待稍后完成的任务。这看起来很愚蠢,但在更复杂的生产代码中可能会发生类似的情况,并且由于缺乏多线程经验,可能会错误地引入死锁。
这个例子的有趣之处在于Hang 方法内部没有像Task.Wait() 或Task.Result 这样的线程阻塞代码。然后当我附加 VS 调试器时,它只显示主线程正在等待任务完成。但是,没有线程显示代码在使用并行堆栈视图的Hang 方法中停止的位置。
这是我在并行堆栈中的每个线程(总共 3 个)上的调用堆栈:
头 1:
[Managed to Native Transition]
Microsoft.VisualStudio.HostingProcess.HostProc.WaitForThreadExit
Microsoft.VisualStudio.HostingProcess.HostProc.RunParkingWindowThread
System.Threading.ThreadHelper.ThreadStart_Context
System.Threading.ExecutionContext.RunInternal
System.Threading.ExecutionContext.Run
System.Threading.ExecutionContext.Run
System.Threading.ThreadHelper.ThreadStart
线程 2:
[Managed to Native Transition]
Microsoft.Win32.SystemEvents.WindowThreadProc
System.Threading.ThreadHelper.ThreadStart_Context
System.Threading.ExecutionContext.RunInternal
System.Threading.ExecutionContext.Run
System.Threading.ExecutionContext.Run
System.Threading.ThreadHelper.ThreadStart
主线程:
System.Threading.Monitor.Wait
System.Threading.Monitor.Wait
System.Threading.ManualResetEventSlim.Wait
System.Threading.Tasks.Task.SpinThenBlockingWait
System.Threading.Tasks.Task.InternalWait
System.Threading.Tasks.Task.Wait
System.Threading.Tasks.Task.Wait
ConsoleApplication.Program.Main Line 12 //this is our Main function
[Native to Managed Transition]
[Managed to Native Transition]
System.AppDomain.ExecuteAssembly
Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly
System.Threading.ThreadHelper.ThreadStart_Context
System.Threading.ExecutionContext.RunInternal
System.Threading.ExecutionContext.Run
System.Threading.ExecutionContext.Run
System.Threading.ThreadHelper.ThreadStart
有没有办法找出任务在Hang 方法中停止的位置?如果可能的话,调用堆栈?我相信在内部必须有一些关于每个任务及其延续点的状态,以便调度程序可以工作。但我不知道如何检查。
【问题讨论】:
-
“任务”窗口怎么样?至少它会显示任务已安排,但尚未开始,并且操作是
<Hang>d_1或类似的。 -
@mikez 很高兴您提到了“任务”窗口,我不认为它真的很有帮助。
d_1 只是编译器生成代码中的状态机名称。您可以使用 DotPeek 查看编译器生成的代码。 -
是的,我意识到这是尽可能少的信息。它只告诉您等待激活的方法的名称(正如您指出的那样由编译器生成),但我认为没有其他任何东西。如果有我很想看看。
-
我发现来自 Eric Lippert 的 quote 很有趣:“在延续传递风格中,根本没有堆栈,根本没有办法告诉你来自哪里;延续对象没有这些信息. 它只知道你下一步要去哪里. ... 在下一个版本中,如果你使用异步功能,你基本上将放弃基于堆栈的编程;将无法查看调用堆栈并知道你是如何到这里,因为堆栈经常是空的。”
标签: c# visual-studio debugging async-await