经过一个月的休息,我昨天继续研究我的计划。我没有改变代码,但现在我的应用程序不再启动了。有一点它只是中断了执行并且似乎陷入了死锁,但是我不确定它是否真的是一个死锁,因为它发生在方法返回时 - 通常不应该发生。
我无法向您展示代码,因为它非常庞大。但我可以肯定地说,超出它自己的线程的唯一动作是访问一些由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异常,但即使是那些也被抛出。这很奇怪,我通常会认为这种锁定确实发生在中间的某个地方,而不是在方法退出后......
答案 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个前提条件。如果其中一个丢失,就不会陷入僵局。 这些先决条件是:
最后一个也可以命名为#34; Timing"。由于它取决于Windows如何分配CPU时间,因此您可能会在没有死锁的情况下生存多年。它更可能出现在多核CPU上,因为如果两个线程真正并行执行,则更容易实现循环等待。
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线程后,您还可以更好地理解async
和await
。
然后我同意Peter Duniho谁说:
如果您只访问GUI线程上的_splashText字段 - 即在该线程中由WPF直接调用的代码中,或者您已明确分派到该线程的代码中 - 那么是...您不要#39; t需要任何其他锁定,因为该字段的所有访问都将在该单个线程中同步发生。
请注意,不仅有" GUI线程"。我看到越来越多的开发人员创建了几个UI线程,即具有自己的消息队列的线程。我希望你只有一个。