执行停止可能的死锁

时间:2014-11-06 13:10:50

标签: c# .net deadlock windbg sosex

经过一个月的休息,我昨天继续研究我的计划。我没有改变代码,但现在我的应用程序不再启动了。有一点它只是中断了执行并且似乎陷入了死锁,但是我不确定它是否真的是一个死锁,因为它发生在方法返回时 - 通常不应该发生。

我无法向您展示代码,因为它非常庞大。但我可以肯定地说,超出它自己的线程的唯一动作是访问一些由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运行。它会检查Symbol服务器,最后的输出如下:

  

CLRDLL:无法通过mscorwks搜索找到mscordacwks_AMD64_x86_4.0.30319.34209.dll   CLRDLL:无法在路径上找到“SOS_AMD64_x86_4.0.30319.34209.dll”   无法自动加载SOS   CLRDLL:已加载的DLL mscordacwks_AMD64_x86_4.0.30319.34209.dll   CLR DLL状态:已加载的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 ......   没有检测到死锁。

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

3 个答案:

答案 0 :(得分: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());    
}

答案 1 :(得分: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();
        });
    }
}

答案 2 :(得分:1)

死锁前置条件(为什么你之前没有看到死锁)

发生死锁必须满足4个前提条件。如果其中一个丢失,就不会陷入僵局。 这些先决条件是:

  • 互相排斥
  • No Preemption
  • 等待
  • 循环等待

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

您的符号(为什么您无法加载SOSEX)

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

该名称表示您正在尝试使用64位调试器调试32位应用程序。微软不支持这一点。您只能在32位WinDbg(也可在64 bis OS BTW上运行)中调试64位WinDbg和32位.NET应用程序中的64位.NET应用程序。

如果您只有64位转储文件并且无法重现该问题,那么您很不走运。没有办法(我多次研究过)使其工作,并且无法将转储从64位转换为32位。

解决问题

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

我不同意让代码运行as proposed in the answer的同步Jakob Christensen。虽然你可以在一个小应用程序中做到这一点,但这需要在更大的应用程序中进行过多的重写。

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

恕我直言,真正了解死锁(需要对Windows Internals有所了解,所以你可能想要阅读the book)。了解Windows线程后,您还可以更好地理解asyncawait

然后我同意Peter Duniho谁说:

  

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

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