D3DImage.Lock触发调度器处理事件致状态异常的技术咨询
我开发了一个使用D3dImage类的WPF应用,订阅了CompositionTarget.Rendering事件,通过以下代码更新显示内容:
D3DImageInstance.Lock(); D3DImageInstance.SetBackBuffer(...); D3DImageInstance.AddDirtyRect(new Int32Rect(0, 0, Width, Height)); D3DImageInstance.Unlock();
调整窗口大小时,Lock()调用期间会触发Resize事件,程序跳转到事件处理函数,调用堆栈如下:
D3DWPFImageSource.Initialize(D3DImageExtManager d3dImageManager, int width, int height, SharpDX.Direct3D11.Texture2D backBufferTexture, SharpDX.Direct3D11.Texture2DDescription textureDesc, int renderSurfaceCount) Line 61 C# D3DRenderContextWPF.InitResourceBuffers(System.Drawing.Size size) Line 4407 C# D3DRenderContextWPF.Resize(System.Drawing.Size size) Line 4589 C# Workspace.OnResize(System.EventArgs e) Line 3875 C# WorkspaceBase.CreateAndBindTargets() Line 1670 C# WorkspaceBase.OnRenderSizeChanged(System.Windows.SizeChangedInfo sizeInfo) Line 1845 C# [External Code] > D3DImageInstance.Lock(); D3DImageExtManager.UpdateBackBufferCommand.Update(SharpDX.Direct3D11.Device device, SharpDX.Direct3D11.DeviceContext context, SharpDX.Direct3D11.Texture2D wpfSharedSurface, System.IntPtr sharedSurfacePtr) Line 222 C# D3DImageExtManager.CompositionTargetOnRendering(object sender, System.EventArgs eventArgs) Line 156 C# D3DRenderContextWPF.OnRendering() Line 4491 C# WorkspaceBase.OnRendering(object sender, System.EventArgs e) Line 1785 C# [External Code]
在Resize事件处理函数中会重新初始化图形资源,导致回到Rendering处理函数时程序处于不一致状态。想知道:
- 这是正常行为吗?
- 是否仅由
Lock()调用导致? - 该如何正确处理这种情况?
1. 行为是否正常?
这是WPF渲染机制下的正常行为。CompositionTarget.Rendering事件在WPF渲染线程触发,窗口Resize事件(SizeChanged)也可能在同一线程被调度执行。调用D3DImage.Lock()时,WPF内部会触发渲染系统的同步操作,间接导致未处理的Resize事件被立即处理,从而出现嵌套调用的情况。
2. 是否仅由Lock()导致?
不完全是,但Lock()是触发嵌套场景的关键节点。D3DImage.Lock()会获取WPF渲染系统的内部锁,这个过程中可能会触发未处理的布局更新或窗口消息(包括Resize)被同步处理。如果没有Lock(),Rendering事件和Resize事件通常按顺序执行,不会出现嵌套。
3. 正确处理方式
用状态标记避免嵌套冲突:
在Rendering事件处理函数中设置isRendering布尔标记,进入时设为true,退出时设为false。Resize事件处理时先检查该标记,若当前正在渲染,就通过Dispatcher.BeginInvoke将资源初始化逻辑延迟到下一个消息循环执行。示例代码:private bool _isRendering; void CompositionTarget_OnRendering(object sender, EventArgs e) { if (_isRendering) return; _isRendering = true; try { D3DImageInstance.Lock(); // 执行更新逻辑 D3DImageInstance.Unlock(); } finally { _isRendering = false; } } void Workspace_OnResize(object sender, EventArgs e) { if (_isRendering) { // 延迟处理Resize Dispatcher.BeginInvoke(new Action(() => { /* 执行资源重新初始化 */ }), DispatcherPriority.Render); return; } // 正常执行Resize逻辑 InitResourceBuffers(newSize); }分离资源初始化与Rendering逻辑:
将资源初始化放到独立的后台线程(若允许),或确保Resize时的资源替换是原子性操作。比如维护一个"待激活"的资源集合,在Rendering事件中安全切换到新资源,避免直接修改当前正在使用的资源。缩短Lock()到Unlock()的执行时间:
D3DImage.Lock()与Unlock()之间的代码要尽可能简短,减少窗口消息被触发的概率,同时避免调用任何可能触发布局或窗口事件的API。
内容的提问来源于stack exchange,提问作者Simone

