多线程WinForms程序‘Object is currently in use elsewhere’问题排查及锁优化
问题解答
一、异常核心信息解析
(a) 执行失败的例程
从异常堆栈跟踪可明确,失败的是**System.Drawing.Image.get_Width()**方法,该方法在WinForms的PictureBox.OnPaint绘制流程中被调用,用于获取图片宽度以计算绘制区域。
(b) 涉及的具体对象
异常直接关联的是绑定到PbWM(你的PictureBox控件)的Image对象。当UI线程触发PictureBox重绘时,尝试读取该Image的Width属性,发现对象正被其他线程占用。
(c) 当前占用对象的代码
虽然日志未直接给出占用线程的代码,但结合你的多线程图片生成逻辑,大概率是某个后台线程正在对该Bitmap对象进行修改、克隆或其他操作(比如你代码中4个阶段的图片生成步骤),而UI线程的绘制操作同时访问了它。
二、为何用了SyncLock仍出现错误
你的现有SyncLock逻辑存在两个关键问题:
- 跨线程操作WinForms控件违规:WinForms控件的属性(包括
PictureBox.Image)只能在UI线程中修改,你的代码直接在后台线程中设置PbWM.Image,即使加了锁,也违反了WinForms的线程安全规则,可能导致控件内部状态混乱。 - 锁的范围和对象选择不当:你锁的是
bm对象,但bm可能在其他未加锁的代码段被后台线程操作;同时,当你将bm.Clone赋值给PbWM.Image后,UI线程绘制时访问的是克隆后的Image对象,如果该克隆对象在后台线程中仍被引用并操作,也会引发冲突。
三、System.Transactions出现在日志中的原因
这是.NET框架的系统追踪机制导致的,并非你的代码引用了该组件。.NET会通过System.Transactions模块记录关键的未处理异常追踪信息,属于系统级日志的一部分,和你的业务代码无关。
四、三种锁实现的优劣对比
第一种实现
' The Thread enters with Bitmap bm1.. ' I make a working copy of bm1 Using bm0 As Bitmap = CType(bm1.Clone, Bitmap) Using gr As Graphics = Graphics.FromImage(bm) ' Some image drawing is done here End Using SyncLock bm ' Replace bm1 with the altered image bm1 = CType(bm0.Clone, Bitmap) End SyncLock End Using
- 相对合理,但存在缺陷:锁的是
bm这个Bitmap数据对象本身,而Bitmap是可变对象,如果后续bm被重新赋值,新的bm对象会脱离原锁的控制,导致同步失效;同时,如果bm在其他代码段未通过SyncLock bm访问,依然会有并发问题。
第二种实现
' The Thread enters with Bitmap bm1.. ' I make a working copy of bm1 Using bm0 As Bitmap = CType(bm1.Clone, Bitmap) Using gr As Graphics = Graphics.FromImage(bm) ' Some image drawing is done here End Using Dim thisLock As New Object SyncLock thisLock ' Replace bm1 with the altered image bm1 = CType(bm0.Clone, Bitmap) End SyncLock End Using
- 完全无效:
thisLock是方法内局部变量,每个线程进入都会创建新的锁对象,不同线程的SyncLock无法形成同步,完全起不到线程安全的作用。
第三种实现
' The Thread enters with Bitmap bm1.. ' I make a working copy of bm1 Using bm0 As Bitmap = CType(bm1.Clone, Bitmap) Using gr As Graphics = Graphics.FromImage(bm) ' Some image drawing is done here End Using Dim thisLock As New Object SyncLock thisLock ' Replace bm1 with the altered image bm1 = New Bitmap(bm0) End SyncLock End Using
- 同样无效:和第二种问题一致,局部创建的
thisLock无法在多线程间形成同步,锁机制完全失效;仅New Bitmap(bm0)比Clone生成的对象更独立(完全复制像素数据),但锁的问题导致同步毫无意义。
最优方案建议
放弃以上三种,采用专用静态锁对象+UI线程更新控件的组合:
- 定义全局静态锁对象,避免锁数据对象:
Private Shared ReadOnly _imageLock As New Object() - 后台线程处理图片时,用该锁同步共享的Bitmap对象:
' 后台线程代码 Using bm0 As Bitmap = CType(bm1.Clone, Bitmap) Using gr As Graphics = Graphics.FromImage(bm0) ' 执行图片绘制操作 End Using SyncLock _imageLock ' 先释放旧对象 If bm1 IsNot Nothing Then bm1.Dispose() End If bm1 = New Bitmap(bm0) End SyncLock ' 切换到UI线程更新PictureBox PbWM.Invoke(Sub() PbWM.Image?.Dispose() PbWM.Image = CType(bm1.Clone, Bitmap) End Sub) End Using
内容的提问来源于stack exchange,提问作者Tim Makins
相关产品推荐
相关产品推荐

