gpt4 book ai didi

c# - 如何找出死锁等待的任务及其当前调用堆栈?

转载 作者:行者123 更新时间:2023-11-30 12:22:17 30 4
gpt4 key购买 nike

这是一个简化示例,我发现在某些情况下很难调试等待任务中的死锁:

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 调试器时,它只显示主线程正在等待任务完成。但是,没有线程显示代码在使用并行堆栈 View 的 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 方法中停止的位置?如果可能的话调用堆栈?我相信在内部必须有一些关于每个任务及其延续点的状态,这样调度程序才能工作。但我不知道如何检查。

最佳答案

在 Visual Studio 内部,我不知道有什么方法可以简单地调试这种情况。但是,对于完整的框架应用程序,还有其他两种方法可以将其可视化,此外还有在 .NET Core 3 中执行此操作的额外预览。

tldr 版本:是的,这很难,而且是的,你想要的信息就在那里,只是很难找到。按照以下方法找到堆对象后,您可以在 VS 监 window 口中使用它们的地址来使用可视化工具进行更深入的研究。

WinDbg

WinDbg 有一个原始但有用的扩展,它提供了一个 !dumpasync命令。

如果您从 vs-threading 下载扩展程序释放分支并将 x64 和 x86 AsyncDebugTools.dll 复制到 C:\Program Files (x86)\Windows Kits\10\Debuggers\[x86|x64]\winext 文件夹,您可以执行以下操作:

.load AsyncDebugTools
!dumpasync

输出(取自上面的链接)如下所示:

07494c7c <0> Microsoft.Cascade.Rpc.RpcSession+<SendRequestAsync>d__49
.07491d10 <1> Microsoft.Cascade.Agent.WorkspaceService+<JoinRemoteWorkspaceAsync>d__28
..073c8be4 <5> Microsoft.Cascade.Agent.WorkspaceService+<JoinWorkspaceAsync>d__22
...073b7e94 <0> Microsoft.Cascade.Rpc.RpcDispatcher`1+<>c__DisplayClass23_2+<<BuildMethodMap>b__2>d[[Microsoft.Cascade.Contracts.IWorkspaceService, Microsoft.Cascade.Common]]
....073b60e0 <0> Microsoft.Cascade.Rpc.RpcServiceUtil+<RequestAsync>d__3
.....073b366c <0> Microsoft.Cascade.Rpc.RpcSession+<ReceiveRequestAsync>d__42
......073b815c <0> Microsoft.Cascade.Rpc.RpcSession+<>c__DisplayClass40_1+<<Receive>b__0>d

在上面的示例中,输出不太有趣:

033a23c8 <0> StackOverflow41476418.Program+<Hang>d__1

输出的描述是:

The output above is a set of stacks – not exactly callstacks, but actually "continuation stacks". A continuation stack is synthesized based on what code has 'awaited' the call to an async method. It's possible that the Task returned by an async method was awaited from multiple places (e.g. the Task was stored in a field, then awaited by multiple interested parties). When there are multiple awaiters, the stack can branch and show multiple descendents of a given frame. The stacks above are therefore actually "trees", and the leading dots at each frame helps recognize when trees have multiple branches.

If an async method is invoked but not awaited on, the caller won't appear in the continuation stack.

一旦您看到更复杂情况的嵌套层次结构,您至少可以深入研究状态对象并找到它们的延续和根源。

LinqPad 和 ClrMd

另一个有用的是 LinqPad再加上 ClrMdClrMD.Extensions .后一个包用于将 ClrMd 桥接到 LINQPad - 有一个 getting started guide .一旦你设置了包/命名空间,这个查询就是你想要的:

var session = ClrMD.Extensions.ClrMDSession.LoadCrashDump(@"dmpfile.dmp");
var stateMachineTypes = (
from type in session.Heap.EnumerateTypes()
where type.Interfaces.Any(item => item.Name == "System.Runtime.CompilerServices.IAsyncStateMachine")
select type);
session.Heap.EnumerateDynamicObjects(stateMachineTypes).Dump(2);

下面是在您的示例代码上运行的输出示例: The result of the above query on your sample code

点网核心 3

对于 .NET Core 3.x,他们将 !dumpasync 添加到 WinDbg sos 扩展中。它比上面描述的扩展要好得多,因为它提供了更多的上下文。可以看到是part of a much larger user story改进异步代码的调试。下面是 .NET Core 3.0 预览版 6 下的输出,带有扩展选项的 SOS 预览版 7。请注意,存在行号,这是您使用上述选项无法获得的。:

0:000> !dumpasync -stacks -roots
Statistics:
MT Count TotalSize Class Name
00007ffb564e9be0 1 96 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[StackOverflow41476418_Core.Program+<Hang>d__1, StackOverflow41476418_Core]]
Total 1 objects
In 1 chains.
Address MT Size State Description
00000209915d21a8 00007ffb564e9be0 96 0 StackOverflow41476418_Core.Program+<Hang>d__1
Async "stack":
.00000209915d2738 System.Threading.Tasks.Task+SetOnInvokeMres
GC roots:
Thread bc20:
000000e08057e8c0 00007ffbb580a292 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken) [/_/src/System.Private.CoreLib/shared/System/Threading/Tasks/Task.cs @ 2939]
rbp+10: 000000e08057e930
-> 00000209915d21a8 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[StackOverflow41476418_Core.Program+<Hang>d__1, StackOverflow41476418_Core]]

000000e08057e930 00007ffbb580a093 System.Threading.Tasks.Task.InternalWaitCore(Int32, System.Threading.CancellationToken) [/_/src/System.Private.CoreLib/shared/System/Threading/Tasks/Task.cs @ 2878]
rsi:
-> 00000209915d21a8 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[StackOverflow41476418_Core.Program+<Hang>d__1, StackOverflow41476418_Core]]

000000e08057e9b0 00007ffbb5809f0a System.Threading.Tasks.Task.Wait(Int32, System.Threading.CancellationToken) [/_/src/System.Private.CoreLib/shared/System/Threading/Tasks/Task.cs @ 2789]
rsi:
-> 00000209915d21a8 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[StackOverflow41476418_Core.Program+<Hang>d__1, StackOverflow41476418_Core]]

Windows symbol path parsing FAILED
000000e08057ea10 00007ffb56421f17 StackOverflow41476418_Core.Program.Main(System.String[]) [C:\StackOverflow41476418_Core\Program.cs @ 12]
rbp+28: 000000e08057ea38
-> 00000209915d21a8 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[StackOverflow41476418_Core.Program+<Hang>d__1, StackOverflow41476418_Core]]

000000e08057ea10 00007ffb56421f17 StackOverflow41476418_Core.Program.Main(System.String[]) [C:\StackOverflow41476418_Core\Program.cs @ 12]
rbp+30: 000000e08057ea40
-> 00000209915d21a8 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[StackOverflow41476418_Core.Program+<Hang>d__1, StackOverflow41476418_Core]]

关于c# - 如何找出死锁等待的任务及其当前调用堆栈?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41476418/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com