由 Dispatcher.Invoke 从多个线程调用的代码中的死锁
Posted
技术标签:
【中文标题】由 Dispatcher.Invoke 从多个线程调用的代码中的死锁【英文标题】:Deadlock in code invoked by Dispatcher.Invoke from multiple threads 【发布时间】:2019-08-26 11:26:55 【问题描述】:我在混合模式 C#/C++ 应用程序中遇到了一个奇怪的问题,当多个线程在调度程序线程上运行相同的函数时,该应用程序最终会出现死锁。
代码的大体结构是这样的:
在 C++ 中:
std::thread t([this,req, dataProvider]()
AsyncRequestData(req, dataProvider);
);
SetThreadPriority(t.native_handle(), THREAD_PRIORITY_BELOW_NORMAL);
t.detach();
在 AsyncRequestData 内部,我交给了一个 C# 方法,该方法使用 Dispatcher 来运行一些 WPF 呈现代码:
在 C# 中
_dispatcher.Invoke(() =>
var tileRender = new WpfTileRenderer(tileId);
_drawObjectStyleProvider.RefreshTiledStyle = true;
tileRender.Render(Viewport, filteredRenderList);
);
在 WpfTileRenderer 内部是一个新的跳转回本地代码,它执行线程锁定。 (我们使用 QReadWriteLock,但我怀疑我们会得到与 std::mutex 或任何其他同步对象相同的结果):
void UpdatePointSymbol(int idx, const BasicGeoTypes::GeoPos& pos)
QWriteLocker lock(&_rwlock);
...
似乎发生的情况是,我们有两个请求都被移交给调度程序线程(如预期的那样),但是当第一个请求到达 QWriteLocker 时,执行跳转到新请求的开始。当第二个请求到达同一个 QWriteLocker 时,它会陷入死锁。
堆栈看起来像这样:
NativeBridge.UpdatePointSymbol
[External Code]
WpfTileRenderer.Render
[External Code]
NativeBridge.UpdatePointSymbol
[External Code]
WpfTileRenderer.Render
[External Code]
我知道这是一团糟,如果我们能避免在本机代码和托管代码之间来回切换,我会是一个更快乐的人,但恐怕这不会很快发生。
所以我要问的基本上是是否有已知的原因会发生这种情况。即调度程序是否可以部分运行一个方法,然后通过从调用它的不同线程启动一个新方法来继续?
根据文档 Dispatcher.Invoke 应该在当前调度程序线程上同步执行,所以我认为不是?
关于这种行为的其他原因有什么想法吗?
【问题讨论】:
请分享您的Render
方法。
WPF 背负着一个糟糕的 Google 热门热门歌曲,始终青睐 BeginInvoke() 而不是 Invoke()。 C++ 背负着糟糕的微优化,总是喜欢支持递归的锁。
在一个线程上同步,但不能跨多个线程。您只在更新期间锁定?插入和读取呢?我喜欢做的是有一个 Read_Write(Mode mode, ref data) ,其中 Mode 是读或写。然后将lock in method和use method用于所有的读写。这样你就不能同时读写了。
【参考方案1】:
看来这个问题是由代码中其他地方的越界数组操作引起的。我怀疑这已经把堆栈搞得一团糟,以至于我得到了我上面描述的明显情况。
我以前见过类似的事情,应该在发帖前仔细检查一下。感谢那些回答的人,我真的很抱歉浪费了你的时间:-)
【讨论】:
以上是关于由 Dispatcher.Invoke 从多个线程调用的代码中的死锁的主要内容,如果未能解决你的问题,请参考以下文章
使用 Dispatcher.Invoke 从非主线程更改 WPF 控件
我怎样才能给 Dispatcher.Invoke 一个论点?
等待 Dispatcher.InvokeAsync 与 Dispatcher.Invoke
Application.Current.Dispatcher.BeginInvoke(action) VS。 Application.Current.Dispatcher.Invoke(action)