VB.NET RaiseEvent, threadsafe? - vb.net

Is RaiseEvent thread safe?
In C# you write
if (event != null)
{
event.invoke();
}
and the C# code is not thread safe....

If I need to do thread safe events, I write this:
Class Test
Public Event Click(ByVal sender As Object, ByVal e As EventArgs)
Public Event MouseIn(ByVal sender As Object, ByVal e As EventArgs)
Private Delegate Sub EventArgsDelegate(ByVal e As EventArgs)
Private ReadOnly _parent As Control
Public Sub New(ByVal parent As Control)
_parent = parent
End Sub
Private Sub OnClick(ByVal e As EventArgs)
If _parent.InvokeRequired Then
_parent.Invoke(New EventArgsDelegate(AddressOf OnClick), e)
Else
RaiseEvent Click(Me, e)
End If
End Sub
Private Sub OnMouseIn(ByVal e As EventArgs)
If _parent.InvokeRequired Then
_parent.Invoke(New EventArgsDelegate(AddressOf OnMouseIn), e)
Else
RaiseEvent MouseIn(Me, e)
End If
End Sub
End Class
Then whenever I need to raise the event I just use the OnClick(new eventargs(...)), etc. If you use Reflector you can observe that most thread safe controls use a similar system.

In C# you rather write:
EventHandler h= myEvent;
if (h!=null)
h(...);
This avoids the obvious problem (i.e. the unsubscribing between the test and the call), but this isn't thread-safe either.
Calling an event implies the listener is ready to process this event. This heavily depends on your specific situation, and is usually achieved through the use of synchronization mechanisms.
What exactly do you mean by "Thread safe" ?

Related

How to raise an event when object created

Why event handler dose not respond to the event when the object created
when I tried to raise a private event at the same way the private event raised and handled as expected
Public Class Form1
Dim WithEvents nClass As Class1
Private Sub nClass_Created(ByVal sender As Object, ByVal e As System.EventArgs) Handles nClass.Created
MessageBox.Show("Created !")
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
nClass = New Class1
End Sub
End Class
Public Class Class1
Event Created(ByVal sender As Object, ByVal e As EventArgs)
Sub New()
'some codes
'when finish
RaiseEvent Created(Me, New EventArgs)
End Sub
End Class
This concept is broken, logically. In order to raise an event, there has to be an event handler attached to the event. In order to attach a handler, the object has to exist. In order to exist the constructor must complete. You'll never be able to have a constructor raise an event because the delegate list will be empty when the constructor is running (no handlers will have been added)
This would be easier to see in C#, this is how we attach events to things:
var thing = new Thing();
thing.Event += new EventHandler(NameOfSomeMethod);
VB has a similar construct:
Dim thing as New Thing()
AddHandler(thing.Event, AddressOf(NameOfSomeMethod))
As you can see, the thing has to be constructed first. The constructor code could certainly invoke the event, but there won't be any handlers attached so nothing will happen
If you want to get notified every time an object is created use a Factory pattern; the class that constructs your class can raise the event that the class has been created

Threads are the bane of my existence

I have a button which on click will run a Sub, creating a process which runs a script.
When this script is finished an Exited handler will fire and run another Sub which cleans up so that the application is ready to go anew without restarting it.
I disable the button during the run and try to re-enable it when the Exit is fired, however it tells me that the button is in another thread. So I tried using SynchronizedContext and Post:
Declared at the start of my class:
Class MainWindow
Private sc As SynchronizationContext = SynchronizationContext.Current
Not sure if I'm doing that correctly but it worked for me elsewhere in the code where I had the same problem.
The exit handling sub:
Private Sub CMD_Exited(ByVal sender As Object, ByVal e As EventArgs)
myProcess.CancelOutputRead()
myProcess.CancelErrorRead()
sc.Post(AddressOf Button_Click, Button1.IsEnabled = True)
Close()
End Sub
Which errors:
Method 'Private Sub Button_Click(sender As Object, e As RoutedEventArgs)' does not have a signature compatible with delegate 'Delegate Sub SendOrPostCallback(state As Object)'.
What can I do here? Changing the button signature will cause incompatibilities elsewhere.
Are there better ways to get around this threads issue?
Visual Vincent is correct, you need to invoke on the UI thread. Specifically you need to read this How to: Make Thread-Safe Calls to Windows Forms Controls.
Public Delegate Sub DoProcessStuffOnUIThreadHandler()
Private Sub CMD_Exited(ByVal sender As Object, ByVal e As EventArgs)
If Me.Button1.InvokeRequired Then
Dim d As New DoProcessStuffOnUIThreadHandler(AddressOf DoProcessStuffOnUIThread)
Me.Button1.Invoke(d)
Else
DoProcessStuffOnUIThread()
End If
End Sub
Private Sub DoProcessStuffOnUIThread()
myProcess.CancelOutputRead()
myProcess.CancelErrorRead()
Button1.IsEnabled = True
Close()
End Sub
(28-SEP-2017) Edit to add an alternative, that I used frequently in my WinForms code days, for brevity:
Public Delegate Sub DoProcessStuffOnUIThreadHandler()
Private Sub CMD_Exited(ByVal sender As Object, ByVal e As EventArgs)
If Me.Button1.InvokeRequired Then
Dim d As New DoProcessStuffOnUIThreadHandler(AddressOf CMD_Exited)
Me.Button1.Invoke(d)
Else
myProcess.CancelOutputRead()
myProcess.CancelErrorRead()
Button1.IsEnabled = True
Close()
End If
End Sub
The added example simply reduces code use. Both examples end in the same result. Hope that helps.

VB.NET Invoke cannot be called on a control until the window handle has been created, BUT the handle is created

This is my situation, there are 2 Classes and my main form Form1:
Class1: has a method doSomethingAndCall(callback) which creates a new thread
Class2: has dynamic created controls with a button that fires Class1.doSomethingAndCall(newCallback)
in code it looks like this (it starts at Class2.Button_Click):
Class Class1
public shared sub doSomethingAndCallAsync(state as object)
Console.WriteLine(Form1.InvokeRequired) 'output: false
Console.WriteLine(Form1.IsHandleCreated) 'output: false
Form1.Invoke(state.callback) 'throws System.InvalidOperationException
end sub
public shared sub doSomethingAndCall(callback as object)
System.Threading.ThreadPool.QueueUserWorkItem(AddressOf doSomethingAndCallAsync, New With {.callback = callback})
end sub
End Class
Class Class2
Public Delegate Sub doSomethingDelegate()
Public Sub doSomething()
Console.WriteLine("success!")
End Sub
Public Sub Button_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Class1.doSomethingAndCall(New doSomethingDelegate(AddressOf doSomething))
End Sub
End Class
The exact exception I get is:
Invoke or BeginInvoke cannot be called on a control until the window handle has been created
and as I can see the console.WriteLine in line 4 shows me that the form is realy not created. So I added this handlers, and now it get's really confusing:
Private Sub Form1_HandleCreated(sender As Object, e As System.EventArgs) Handles Me.HandleCreated
Console.WriteLine("Handle created") 'Output: Handle created, when running program
End Sub
Private Sub Form1_HandleDestroyed(sender As Object, e As System.EventArgs) Handles Me.HandleDestroyed
Console.WriteLine("Handle destroyed") 'Will never Output!
End Sub
So it's created and never destroyed but if i click the button it's nevertheless not avaible? -Can anyone explain me what is going on and how to call a callback correct, thanks!
The instance of My.Forms.Form1 aka. Form1 will be different in each thread. You need a handle to the correct instance. Drop a button onto your Form1 and add the following code:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Threading.Tasks.Task.Factory.StartNew(Sub() Class1.Wrong())
Threading.Tasks.Task.Factory.StartNew(Sub() Class1.Correct(Me))
End Sub
End Class
Public Class Class1
Public Shared Sub Wrong()
Debug.WriteLine(String.Format("(Other thread, wrong) InvokeRequired={0}, IsHandleCreated={1}", Form1.InvokeRequired, Form1.IsHandleCreated))
End Sub
Public Shared Sub Correct(instance As Form1)
Debug.WriteLine(String.Format("(Other thread, correct) InvokeRequired={0}, IsHandleCreated={1}", instance.InvokeRequired, instance.IsHandleCreated))
End Sub
End Class
Output
(Other thread, correct) InvokeRequired=True, IsHandleCreated=True
(Other thread, wrong) InvokeRequired=False, IsHandleCreated=False

Multi parent form

I've a vb .net winform that is show by others forms. I've frmA.vb, frmB.vb, frmC.vb and frmD.vb.
This all forms can call frmItem.vb.
frmItem.vb allows the user to select an item from a Database, this item is sent by calling a Set Property on the parent.
i.e.
I open frmA, click on button (something like:)
fi = new frmItem(frmA) 'frmItem has 4 New() methods, frmA.. b... c and d
'i need to pass the correct parent.
fi.showModal()
So, when i add an item, it calls
fA.addItem(item_id)
It works OK, my doubt is about optimization, because i've duplicated frmItem; one copy managed frmA and frmB, and the other one, frmC and frmD.
i.e.
in frmItem1 when i've to sent the item, i use:
private fB as frmB
private fA as frmA
if parentFrmA is nothing then
'Is frmB
fB.addItem(item_id)
else
'Is frmA
fA.addItem(item_id)
end if
And, on frmItem2:
private fC as frmC
private fD as frmD
if parentFrmC is nothing then
'Is frmD
fD.addItem(item_id)
else
'Is frmC
fC.addItem(item_id)
end if
If i modify frmItem1, i've to modify frmItem2 and viceversa, because they should look and act like one.
All four forms, have the same Set Property, but like they're differents forms, i can't use a unique Form class in frmItem.
Is the posibility that one form, can manage multi parents in an easy way??
If you need more info, let me know. Thanks
I can't completely follow your example since, well, I think it's just hard to follow.
But in general, it sounds like these child forms should just be raising an event that the parent form is listening for. That way, you can separate your concerns a bit and not hardcode these dependencies.
You can try making your own EventArgs class to follow best practices:
Public Class ChildFormEventArgs
Inherits EventArgs
Private _ItemID As Integer
Public Sub New(ByVal itemID As Integer)
_ItemID = itemID
End Sub
ReadOnly Property ItemID() As Integer
Get
Return _ItemID
End Get
End Property
End Class
Your child forms would have a public event and you would raise it when ever this "added" thing happens:
Public Class Form2
Public Event ItemAdded(ByVal sender As Object, ByVal e As ChildFormEventArgs)
Private _ItemID as Integer
Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
RaiseEvent ItemAdded(Me, New ChildFormEventArgs(_ItemID))
End Sub
End Sub
And then your parent form is the one listening and can act accordingly:
Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
Using testForm As New Form2()
AddHandler testForm.ItemAdded, AddressOf ChildForm_ItemAdded
testForm.ShowDialog(Me)
RemoveHandler testForm.ItemAdded, AddressOf ChildForm_ItemAdded
End Using
End Sub
Private Sub ChildForm_ItemAdded(ByVal sender As Object, ByVal e As ChildFormEventArgs)
'// do something here.
'// sender is the child form that called it
'// e is the event arguments that contains the ItemID value
End Sub

C# to VB.Net Syntax conversion

Can any one translate the following syntax to vb.net.
m_TextBox.Loaded += TextBoxLoaded
m_TextBox.Loaded -= TextBoxLoaded;
private void TextBoxLoaded(object sender, RoutedEventArgs e)
{
Init();
}
..
containsTextProp.AddValueChanged(m_TextBox, (sender, args) => UpdateAdorner());
...
private void UpdateAdorner()
{...}
Despite the 25% acceptance rate, here it is:
AddHandler m_TextBox.Loaded, AddressOf TextBoxLoaded
RemoveHandler m_TextBox.Loaded, AddressOf TextBoxLoaded
Private Sub TextBoxLoaded(ByVal sender as Object, ByVal e as RoutedEventArgs)
Init()
End Sub
Your call to AddValueChanged can't be directly translated, as VB.NET's lambda expression support is not as robust as C#'s. In particular, VB.NET lambdas must be an expression, so you must either return a value or call a Function. In your case, you would be calling a Sub, which isn't allowed in VB.NET. You should consider changing the signature of UpdateAdorner to be a standard event handler (like the TextBoxLoaded method) and pass AddressOf UpdateAdoerner to AddValueChanged.
Like this:
containsTextProp.AddValueChanged(m_TextBox, AddressOf UpdateAdorner);
...
Private Sub UpdateAdorner(ByVal sender as Object, ByVal e as EventArgs)
...
End Sub
There are plenty of online converters, you shuold probably try that first next time and post here if it doesn't work or you have a problem.
AddHandler m_TextBox.Loaded, AddressOf TextBoxLoaded ' per #Adam Robinson'
RemoveHandler m_TextBox.Loaded, AddressOf TextBoxLoaded ' per #Adam Robinson'
Private Sub TextBoxLoaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
Init()
End Sub
Private Sub UpdateAdorner()
End Sub
You might find the "C# and VB.NET Comparison Cheat Sheet" useful.
http://aspalliance.com/625
You can toss it in an app, build it, then open the app in .NET reflector. .NET Reflector can take the IL and "turn it into" C#/VB.NET, etc.