gpt4 book ai didi

c# - 在异步/等待应用程序中获取所有堆栈跟踪

转载 作者:行者123 更新时间:2023-12-03 13:16:36 32 4
gpt4 key购买 nike

我想获取有关异步C#应用程序中所有调用堆栈的信息(或获取所有stacktrace)。我知道how to get stacktraces of all existing threads
但是,如何获取有关await 释放的所有调用堆栈的信息,这些堆栈上没有正在运行的线程?

语境示例
假设以下代码:

private static async Task Main()
{
async Task DeadlockMethod(SemaphoreSlim lock1, SemaphoreSlim lock2)
{
await lock1.WaitAsync();
await Task.Delay(500);
await lock2.WaitAsync(); // this line causes the deadlock
}

SemaphoreSlim lockA = new SemaphoreSlim(1);
SemaphoreSlim lockB = new SemaphoreSlim(1);

Task call1 = Task.Run(() => DeadlockMethod(lockA, lockB));
Task call2 = Task.Run(() => DeadlockMethod(lockB, lockA));

Task waitTask = Task.Delay(1000);

await Task.WhenAny(call1, call2, waitTask);

if (!call1.IsCompleted
&& !call2.IsCompleted)
{
// DUMP STACKTRACES to find the deadlock
}
}
我想转储所有堆栈跟踪,即使当前没有其线程的堆栈跟踪,也可以找到死锁。
如果将 await lock2.WaitAsync();行更改为 lock2.Wait();,那么前面已经提到的 get stacktraces of all threads就有可能。但是 如何在没有运行线程的情况下列出所有堆栈跟踪?
防止误解:
  • 该示例非常简化,仅说明了潜在的并发症之一。最初的问题是一个复杂的多线程应用程序,该应用程序运行在服务器上,并且可能发生许多难以研究的与并行相关的问题。
  • 我们不仅可以使用async/await堆栈跟踪列表来查找死锁,还可以将其用于其他目的。因此,请不要建议我如何避免死锁或如何编写多线程应用程序-这不是问题的重点。
  • 您通常可以回答此问题,但是至少可以在.Net Core 3.1上运行的解决方案就足够了。
  • 最佳答案

    I know, how to get stacktraces of all existing threads.


    只是在这里提供一些背景知识。
    在Windows中,线程是OS概念。它们是计划的单位。因此,某处有一个确定的线程列表,因为这是OS调度程序使用的。
    此外,每个线程都有一个调用堆栈。这可以追溯到计算机编程的早期。但是,调用堆栈的目的常常被误解。调用堆栈用作返回位置的序列。当方法返回时,它将调用堆栈参数从堆栈以及返回位置弹出,然后跳到返回位置。
    要记住这一点很重要,因为调用堆栈并不代表代码如何进入一种情况。它代表代码要去的地方,它从当前方法返回。调用堆栈是代码要去的地方,而不是代码的来源。这就是调用堆栈存在的原因:定向将来的代码,而不是辅助诊断。现在,事实证明调用栈确实具有用于诊断的有用信息,因为它可以指示代码的来源以及运行的方向,因此这就是调用栈处于异常状态并且通常用于诊断的原因。但这不是调用栈存在的实际原因;这只是一个快乐的情况。
    现在,输入异步代码。
    在异步代码中,调用堆栈仍表示代码返回的位置(就像所有调用堆栈一样)。但是在异步代码中,调用堆栈不再代表代码的来源。在同步世界中,这两件事是相同的,并且调用堆栈(这是必需的)也可以用来回答“此代码是如何到达这里的?”的问题。在异步世界中,调用堆栈仍然是必需的,但是只能回答以下问题:“这段代码在哪里?”并且无法回答“此代码是如何到达这里的?”的问题。回答“此代码是如何到达这里的?”问题您需要 causality chain
    此外,调用堆栈对于正确操作(在同步和异步环境中)都是必需的,因此编译器/运行时确保它们存在。因果关系链不是必需的,并且它们不是开箱即用的。在同步世界中,调用堆栈恰好是因果关系链,这很好,但是这种快乐的情况并没有延续到异步世界中。

    When a thread is released by await, the stacktrace and all objects along the call stack are stored somewhere.


    不;不是这种情况。如果 async使用了纤维,这将是正确的,但事实并非如此。没有任何地方保存调用堆栈。

    Because otherwise the continuation thread would lose context.


    await恢复时,它仅需要足够的上下文来继续执行其自己的方法,并有可能完成该方法。因此,有一个 async状态机结构被装箱并放置在堆上。此结构包含对局部变量的引用(包括 this和方法参数)。但是,这是程序正确性所必需的。调用堆栈不是必需的,因此不会被存储。
    通过在 await之后设置断点并观察调用堆栈,您可以轻松地自己看到此情况。您会看到在第一个 await产生之后,调用堆栈消失了。或者-更正确地-调用堆栈代表的是继续 async方法的代码,而不是最初启动 async方法的代码。
    在实现级别, async/ await比其他任何东西都更像回调。当方法命中 await时,它将状态机结构粘贴在堆上(如果尚未加载),并连接回调。任务完成时将触发(直接调用)该回调,并继续执行 async方法。该 async方法完成后,它便完成了其任务,然后调用那些任务的任何 await以继续执行。因此,如果完成了整个任务序列,则实际上最终会得到一个因果关系堆栈的倒置的调用堆栈。

    I would like to dump all stacktraces, even those not having its thread currently, so that I can find the deadlock.


    因此,这里有两个问题。首先,没有所有 Task对象(或更笼统地说,类似于任务的对象)的全局列表。那将是一件很难的事情。
    其次,对于每个异步方法/任务,都没有因果关系链。编译器不会生成一个编译器,因为它不需要正确的操作。
    这并不是说这些问题中的任何一个都是不可克服的-只是困难而已。我已经使用 AsyncDiagnostics library完成了因果链问题的一些工作。此时它已经很老了,但是应该可以很容易地升级到.NET Core。它使用PostSharp修改每种方法的编译器生成的代码,并手动跟踪因果关系链。
    但是,AsyncDiagnotics的目标是使因果关系链进入异常。获取所有任务任务的列表并将因果关系链与每个任务任务相关联是另一个问题,可能需要使用附加的探查器。我知道其他想要这种解决方案的公司,但他们都没有专门花费时间来创建一个解决方案。他们所有人都发现实现代码审查,审核和开发人员培训的效率更高。

    关于c# - 在异步/等待应用程序中获取所有堆栈跟踪,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63214165/

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