由 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.Invoke 一个论点?

等待 Dispatcher.InvokeAsync 与 Dispatcher.Invoke

Application.Current.Dispatcher.BeginInvoke(action) VS。 Application.Current.Dispatcher.Invoke(action)

在 Dispatcher.Invoke() 中使用 lambda 表达式作为参数