-
Oct 22nd, 2020, 02:45 PM
#1
[RESOLVED] System Timer and DataBinding
Hi All,
If I have a class that implements INotifyPropertyChanged, and I have a property in that class that is bound to a label on a form, how do I avoid a Cross-threaded exception if I set the property value from a System.Timers.Timer.Elapsed event handler?
The following code demonstrates the exception.
Thanks for looking
Kevin
VB.Net Code:
Imports System.ComponentModel
Imports System.Timers
Public Class Form1
Private thisClass As New aClass
Private lbl As System.Windows.Forms.Label
Public Sub New()
InitializeComponent()
'create the label and add it to the form
lbl = New Label
lbl.Text = "some text"
Me.Controls.Add(lbl)
'set the data binding and start a timer
lbl.DataBindings.Add("Text", thisClass, "X")
End Sub
End Class
Public Class aClass
Implements INotifyPropertyChanged
Private WithEvents tmr As New System.Timers.Timer()
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Sub New()
AddHandler tmr.Elapsed, AddressOf tmr_Elapsed
tmr.Interval = 1000
tmr.Start()
End Sub
Private Sub tmr_Elapsed(sender As Object, e As ElapsedEventArgs)
'change the property value when the timer elapses
X = Guid.NewGuid.ToString
End Sub
Private _x As String = ""
Public Property X As String
Get
Return _x
End Get
Set(value As String)
_x = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("X"))
End Set
End Property
End Class
Last edited by kebo; Oct 22nd, 2020 at 04:05 PM.
Process control doesn't give you good quality, it gives you consistent quality.
Good quality comes from consistently doing the right things.
Vague general questions have vague general answers. A $100 donation is required for me to help you if you PM me asking for help. Instructions for donating to one of our local charities will be provided.
______________________________ Last edited by kebo : Now. Reason: superfluous typo's
-
Oct 22nd, 2020, 03:57 PM
#2
Re: System Timer and DataBinding
I'm mainly posting this because I want to subscribe to this thread because I'm not entirely sure. This looks like something JMcIlhinney, .paul., or TG would have a good idea on.
-
Oct 22nd, 2020, 04:04 PM
#3
Re: System Timer and DataBinding
The way to handle it in the original code where the timer is a member of the form is to simply Invoke in the timer elapsed handler...
VB.net Code:
Me.Invoke(New Action(Sub() thisClass.X = Guid.NewGuid.ToString()))
That said, my real problem is when the timer is not in the form and is a member of the class. I have updated the code in the original post to reflect this.
I suppose we'll have to wait for one of the smart kids.
Last edited by kebo; Oct 23rd, 2020 at 08:37 AM.
Process control doesn't give you good quality, it gives you consistent quality.
Good quality comes from consistently doing the right things.
Vague general questions have vague general answers. A $100 donation is required for me to help you if you PM me asking for help. Instructions for donating to one of our local charities will be provided.
______________________________ Last edited by kebo : Now. Reason: superfluous typo's
-
Oct 22nd, 2020, 05:27 PM
#4
Re: System Timer and DataBinding
The way I would do this would be to Post an event to the SynchronizationContext. If you want to see an example of that, look for the UDP class I posted in the CodeBank. That used a background thread as a listener, and when a message was received, it added it to a Queue, then posted an event. By posting to the UI SynchronizationContext, the event gets raised on the UI thread. You'd then set the label in there.
I believe that JMC showed a simpler way to do that than the one that I used in that UDP class, but the one in the UDP class is pretty doggone easy.
My usual boring signature: Nothing
-
Oct 22nd, 2020, 05:33 PM
#5
Re: System Timer and DataBinding
Originally Posted by dday9
I'm mainly posting this because I want to subscribe to this thread because I'm not entirely sure. This looks like something JMcIlhinney, .paul., or TG would have a good idea on.
Hello Dday9,
You can subscribe to a thread without posting: At the top of the thread, there is "Thread Tools" list and in it there is the command "subscribe to this thread..."
The best friend of any programmer is a search engine
"Don't wish it was easier, wish you were better. Don't wish for less problems, wish for more skills. Don't wish for less challenges, wish for more wisdom" (J. Rohn)
“They did not know it was impossible so they did it” (Mark Twain)
-
Oct 22nd, 2020, 07:10 PM
#6
Re: System Timer and DataBinding
Originally Posted by Shaggy Hiker
The way I would do this would be to Post an event to the SynchronizationContext. If you want to see an example of that, look for the UDP class I posted in the CodeBank. That used a background thread as a listener, and when a message was received, it added it to a Queue, then posted an event. By posting to the UI SynchronizationContext, the event gets raised on the UI thread. You'd then set the label in there.
I believe that JMC showed a simpler way to do that than the one that I used in that UDP class, but the one in the UDP class is pretty doggone easy.
If you have code that is not UI-related itself but may affect an application's UI and you want to make it thread-safe then the SynchronizationContext is definitely the way to go. The Send method is akin to Invoke and Post is akin to BeginInvoke but you don't need access to a control created on the UI thread in order to call them. You just need to make sure that you create your SynchronizationContext instance on the UI thread in the first place. That's not usually a problem because you'll generally be creating the parent object on the UI thread and you just create the SynchronizationConext at the same time.
-
Oct 23rd, 2020, 09:22 AM
#7
Re: System Timer and DataBinding
Thanks Shaggy.
I have this resolved using the code below. I've added a SynchronizationContextStringArgs class that is used to pass a property name and value to the SynchronizationContext.Post method. In the method, I then use reflection to set the property value (via casting with CtypeDynamic) which in turn raises the PropertyChange event and updates the label.
VB.net Code:
Imports System.ComponentModel
Imports System.Reflection
Imports System.Timers
Public Class Form1
Private thisClass As New aClass
Private lbl As System.Windows.Forms.Label
Public Sub New()
InitializeComponent()
'create the label and add it to the form
lbl = New Label
lbl.Text = "some text"
Me.Controls.Add(lbl)
'set the data binding and start a timer
lbl.DataBindings.Add("Text", thisClass, "X")
End Sub
End Class
Public Class aClass
Implements INotifyPropertyChanged
Private WithEvents tmr As New System.Timers.Timer()
Private Shared myContext As System.Threading.SynchronizationContext
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Sub New()
myContext = System.Threading.SynchronizationContext.Current
AddHandler tmr.Elapsed, AddressOf tmr_Elapsed
tmr.Interval = 1000
tmr.Start()
End Sub
Private Sub tmr_Elapsed(sender As Object, e As ElapsedEventArgs)
Dim newValue As String = Guid.NewGuid.ToString
Dim args As New SynchronizationContextStringArgs
args.PropertyName = "X"
args.NewValue = newValue
[B]myContext.Post(AddressOf setProperty, args)[/B]
End Sub
Private _x As String = ""
Public Property X As String
Get
Return _x
End Get
Set(value As String)
_x = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("X"))
End Set
End Property
Private Sub setProperty(e As SynchronizationContextStringArgs)
Dim prop As PropertyInfo = Me.GetType().GetProperty(e.PropertyName, BindingFlags.Public Or BindingFlags.Instance)
If (prop IsNot Nothing) Then
prop.SetValue(Me, CTypeDynamic(e.NewValue, prop.PropertyType), Nothing)
End If
End Sub
End Class
Public Class SynchronizationContextStringArgs
Inherits EventArgs
Public Property PropertyName As String
Public Property NewValue As Object
End Class
Last edited by kebo; Oct 23rd, 2020 at 09:37 AM.
Process control doesn't give you good quality, it gives you consistent quality.
Good quality comes from consistently doing the right things.
Vague general questions have vague general answers. A $100 donation is required for me to help you if you PM me asking for help. Instructions for donating to one of our local charities will be provided.
______________________________ Last edited by kebo : Now. Reason: superfluous typo's
-
Oct 23rd, 2020, 11:04 AM
#8
Re: [RESOLVED] System Timer and DataBinding
As I was moving this code into my project, I realized it could be simplified greatly by posting to a ThreadSafe_PropertyChanged method directly from the property setter. That method has a parameter for the property name and it raises the Property_Change method. The following code illustrates this.
VB.net Code:
Imports System.ComponentModel
Imports System.Reflection
Imports System.Timers
Public Class Form1
Private thisClass As New aClass
Private lblX As System.Windows.Forms.Label
Private lblY As System.Windows.Forms.Label
Public Sub New()
InitializeComponent()
'create the label and add it to the form
lblX = New Label
lblX.Text = "X label"
Me.Controls.Add(lblX)
lblY = New Label
lblY.Location = New Point(0, 30)
lblY.Text = "Y label"
Me.Controls.Add(lblY)
'set the data binding and start a timer
lblX.DataBindings.Add("Text", thisClass, "X")
lblY.DataBindings.Add("Text", thisClass, "Y")
End Sub
End Class
Public Class aClass
Implements INotifyPropertyChanged
Private WithEvents tmr As New System.Timers.Timer()
Private myContext As System.Threading.SynchronizationContext
Private rnd As New Random
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Sub New()
myContext = System.Threading.SynchronizationContext.Current
AddHandler tmr.Elapsed, AddressOf tmr_Elapsed
tmr.Interval = 1000
tmr.Start()
End Sub
Private Sub tmr_Elapsed(sender As Object, e As ElapsedEventArgs)
X = Guid.NewGuid.ToString()
Y = rnd.NextDouble()
End Sub
Private _x As String = ""
Public Property X As String
Get
Return _x
End Get
Set(value As String)
_x = value
myContext.Post(AddressOf ThreadSafe_PropertyChanged, "X")
End Set
End Property
Private _y As Double = 0
Public Property Y As String
Get
Return _y
End Get
Set(value As String)
_y = value
myContext.Post(AddressOf ThreadSafe_PropertyChanged, "Y")
End Set
End Property
Private Sub ThreadSafe_PropertyChanged(propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
Process control doesn't give you good quality, it gives you consistent quality.
Good quality comes from consistently doing the right things.
Vague general questions have vague general answers. A $100 donation is required for me to help you if you PM me asking for help. Instructions for donating to one of our local charities will be provided.
______________________________ Last edited by kebo : Now. Reason: superfluous typo's
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|