1

经过一个月的休息后,我昨天继续研究我的程序。我没有更改代码上的任何内容,但现在我的应用程序不再启动。在某一时刻,它只是中断执行并且似乎陷入死锁,尽管我不确定它是否真的是死锁,因为它在方法返回时发生 - 在通常不应该发生的地方。

我不能给你看代码,因为它很大。但我可以肯定地说,除了它自己的线程之外,唯一的操作是访问一些由 Dispatcher 调用的 UI 元素。直到昨天一切正常,我没有改变任何东西。

这是它发生的地方:

    internal override Task InitializeAddIns()
    {
        try
        {
            Action action = () => this._addinProvider.InitializeAddins();
            Task t = Task.Factory.StartNew(action);
            return t;
        }
        catch (Exception ex)
        {
            Debugger.Break();
            return null;
        }
    }

调用代码:

// Initialize AddIns
splash.SplashText = "SplashScreen:step_searchAddIns".Translate();
this._addinSystem.InitializeAddIns();
splash.SplashText = "SplashScreen:step_startAddIns".Translate();
await Task.Run(() => this._addinSystem.RunAddins());

// Resolve libraries with NativeCompressor
splash.SplashText = "SplashScreen:step_resolveDependencies".Translate();

任务启动并返回“t”。InitializeAddins() 方法成功运行以结束(使用调试器检查它 - 日志还显示它完全完成)。下一步是标记“动作”的声明行(完成时)。然后调试结束,没有任何事情发生。甚至没有调用这个 Dispatcher 钩子:

Dispatcher.CurrentDispatcher.Hooks.DispatcherInactive += (sender, args) => this.Update();

我唯一的假设是某处出现了僵局。我无法解释为什么整个执行停止并卡住了。我只是找不到任何线索从哪里开始搜索。我重新编写了新引入的代码并添加了一些扩展锁定方法,这些方法也可以检测死锁。到目前为止没有检测到死锁。

由于不知道是什么原因导致的,所以我尝试使用 WinDbg 和 SOSEX 来查找错误源。可悲的是,我没有让 WinDbg 运行。它确实检查了符号服务器,最后的输出如下:

CLRDLL:无法通过 mscorwks 搜索找到 mscordacwks_AMD64_x86_4.0.30319.34209.dll CLRDLL:无法在路径上找到“SOS_AMD64_x86_4.0.30319.34209.dll” 无法自动加载 SOS CLRDLL:已加载 DLL mscordacwks_AMD64_x863409DLL CLR 状态: 加载 DLL mscordacwks_AMD64_x86_4.0.30319.34209.dll

虽然它显然加载了一些东西,但我在调用 SOSEX 的 !dlk 命令时收到了这条消息:

0:028> !dlk 无法初始化 .NET 数据接口。需要 mscordacwks.dll 版本 4.0.30319.34209。找到并加载正确版本的 mscordacwks.dll。请参阅 .cordll 命令的文档。正在检查 CriticalSections... 未检测到死锁。

所以我真的不知道如何修复这个错误。这种行为可能是什么原因?我什至没有例外。我已经启用了 CLR 异常,但连那些都没有被抛出。这很奇怪,我通常认为这种锁定确实发生在中间的某个地方,而不是在方法退出之后......

4

3 回答 3

1

第一步是尝试同步运行代码,完全不涉及任何任务。

第二步是检查您是否正确等待。例如,您在调用this._addinSystem.InitializeAddIns(). 这意味着InitializeAddIns在您调用 之前,调用可能不会结束RunAddIns。你应该添加这个:

await this._addinSystem.InitializeAddIns();

最后,您可能没有正确等待调用代码。例如,如果您正在等待void返回函数,则调用可能会死锁:

// This may deadlock because you are awaiting a void returning function!
await MyMethod();

//...

public void MyMethod()
{
    await this._addinSystem.InitializeAddIns();
    await Task.Run(() => this._addinSystem.RunAddins());    
}
于 2014-11-06T14:16:18.583 回答
1

我找到了这个问题的根源。这是我的 Splashscreen,一个简单的窗口,可以通过这些方法访问它以更新当前状态(加载哪个 AddIn 等等)。这绝对不是线程安全的(我想知道为什么它之前工作......)。

我在所有属性中将其更改为以下代码。如果可以检查该代码并确认它没有被黑客入侵或不是一种糟糕的方法,那就太好了,因为它看起来确实有点棘手......但到目前为止它仍然有效。

public string SplashText
{
    get
    {
        using (ThreadLock.Lock(_lock))
        {
            return _splashText;
        }
    }
    set
    {
        if (_dispatcher.CheckAccess())
        {
            _splashText = value;
            OnPropertyChanged();
            return;
        }
        _dispatcher.Invoke(() =>
        {
            _splashText = value;
            OnPropertyChanged();
        });
    }
}
于 2014-11-06T14:35:45.160 回答
1

死锁前置条件(为什么你之前没见过死锁)

发生死锁必须满足 4 个先决条件。如果其中一个缺失,就不会出现死锁。这些先决条件是:

  • 互斥
  • 无抢占
  • 等待
  • 循环等待

最后一个也可以命名为“Timing”。由于它取决于 Windows 分配 CPU 时间的方式,因此您可能会多年没有死锁。在多核 CPU 上更可能发生这种情况,因为如果两个线程真正并行执行,则更容易实现循环等待。

你的符号(为什么你不能加载 SOSEX)

mscordacwks_AMD64_x86_4.0.30319.34209.dll是一个不存在的文件。请承认您已将另一个文件重命名为该文件名,因为您已经看到 WinDbg 正在寻找它。

该名称表明您正在尝试使用 64 位调试器调试 32 位应用程序。微软不支持这一点。您只能在 64 位 WinDbg 中调试 64 位 .NET 应用程序,在 32 位 WinDbg 中调试 32 位 .NET 应用程序(顺便说一句,它也可以在 64 bis OS 上运行)。

如果您只有 64 位转储文件并且无法重现该问题,那么您很不幸。没有办法(我研究了几次)让它工作,也没有办法将转储从 64 位转换为 32 位。

解决问题

除此之外,您使用 SOSEX' 的!dlk方法很好。lock它应该检测由 C#语句引起的死锁。

我不同意按照Jakob Christensen的回答中的建议使代码同步运行。虽然您可以在小型应用程序中做到这一点,但这需要在大型应用程序中进行过多的重写。

切换到同步执行并返回到异步可能会再次导致未检测到的情况(时间可能已经改变,只是不太可能导致死锁)。

恕我直言,最好真正理解死锁(这需要对 Windows 内部有一些了解,所以您可能想阅读这本书)。一旦您了解了 Windows 线程,您也可以更好地了解asyncawait.

然后我同意Peter Duniho的说法:

如果您只访问 GUI 线程上的 _splashText 字段 - 即在该线程中由 WPF 直接调用的代码中,或者在您明确分派到该线程上的代码中 - 那么是的......您不需要任何其他锁定,因为该字段的所有访问都将在该单个线程中同步发生。

请注意,不仅有“GUI 线程”。我看到越来越多的开发人员创建了多个 UI 线程,即拥有自己的消息队列的线程。我希望你只有一个。

于 2014-11-06T21:30:59.520 回答