我在WinForms PropertyGrid中显示了一个对象实例。一些属性getter调用最终调用Monitor.Wait的方法。
问题是Monitor.Wait在这种情况下似乎不能可靠地阻止主UI线程,而且看起来PropertyGrid似乎会尝试加载其他属性,这些属性将重新输入代码的关键部分。我想正在发生的事情是应用程序消息循环以某种方式恢复,即使线程应该通过调用Monitor.Wait()来阻止。
我已使用此代码重现了该行为:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim inst As New Test1
Me.PropertyGrid1.SelectedObject = inst
End Sub
End Class
Public Class Test1
Private WithEvents pTimer As New System.Timers.Timer(1000) With {.AutoReset = False}
Dim rand As New Random()
Private pLock1 As New Object
Private pLock2 As New Object
Public Property Prop1 As Integer
Get
Static inst As Integer = 0
inst += 1
Return ReadVal("Prop1", inst)
End Get
Set(value As Integer)
End Set
End Property
Public Property Prop2 As Integer
Get
Static inst As Integer = 0
inst += 1
Return ReadVal("Prop2", inst)
End Get
Set(value As Integer)
End Set
End Property
Public Property Prop3 As Integer
Get
Static inst As Integer = 0
inst += 1
Return ReadVal("Prop3", inst)
End Get
Set(value As Integer)
End Set
End Property
Private pInRead As Boolean = False
Private Function ReadVal(ByVal propName As String, ByVal checkNumber As Integer) As Integer
Dim rtn As Integer = 0
Static inst As Integer = 0
Dim myinst As Integer
SyncLock pLock1
inst += 1
myinst = inst
Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId & ": ReadVal enter pLock1 readval:{0}, propName:{1}, checkNumber{2}", myinst, propName, checkNumber)
If pInRead Then
Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId & ": pInread already true readval:{0}, propName:{1}, checkNumber{2}", myinst, propName, checkNumber)
End If
pInRead = True
SyncLock pLock2
Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId & ": waiting for pulse readval:{0}, propName:{1}, checkNumber{2}", myinst, propName, checkNumber)
System.Threading.Monitor.Wait(pLock2)
Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId & ": pulse received readval:{0}, propName:{1}, checkNumber{2}", myinst, propName, checkNumber)
rtn = rand.Next(1, 100)
End SyncLock
pInRead = False
End SyncLock
Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId & ": ReadVal exit pLock1 readval:{0}, propName:{1}, checkNumber{2}", myinst, propName, checkNumber)
Return rtn
End Function
Private Sub pTimer_Elapsed(sender As Object, e As Timers.ElapsedEventArgs) Handles pTimer.Elapsed
SyncLock pLock2
Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId & ": timer got pLock2")
System.Threading.Thread.Sleep(1000)
Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId & ": timer pulsing")
System.Threading.Monitor.Pulse(pLock2)
End SyncLock
pTimer.Start()
End Sub
Public Sub New()
pTimer.Start()
End Sub
End Class
要生成行为,我只需要单击Button1,然后单击PropertyGrid几次。 以下是控制台输出部分,显示发生的问题:
8: ReadVal enter pLock1 readval:9, propName:Prop3, checkNumber3
8: waiting for pulse readval:9, propName:Prop3, checkNumber3
8: ReadVal enter pLock1 readval:10, propName:Prop1, checkNumber5
11: timer got pLock2
8: pInread already true readval:10, propName:Prop1, checkNumber5
所以问题,如输出中所示,是在主线程(线程ID 8)中输入ReadVal以填充PropertyGrid中的Prop3,调用Monitor.Wait,而不是完全阻塞线程直到Pulse从pTimer_Elapsed,我们看到ReadVal以某种方式从同一个线程(线程ID 8)输入AGAIN以填充PropertyGrid中的Prop1。 由于它是同一个线程,因此Synclock实际上无法防止重新进入的关键块。在这个例子中它并不重要,但在我的实际应用程序中,我正在访问锁定部分中的共享资源,并且重新进入会导致一些大问题。
为什么调用Monitor.Wait时主线程无法可靠阻塞?知道它没有,解决这个问题的最佳方法是什么?到目前为止,我的解决方案是始终强制ReadVal进入新线程,但必须有更好的方法。