【发布时间】: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用于所有的读写。这样你就不能同时读写了。