Delegates For Events - vb.net

I use BackgroundWorkers occasionally to update another thread while not freezing the UI. I use this code often as it works well:
Private Delegate Sub DelegateUpdateStatus(ByVal statusText As String)
Private Sub UpdateStatus(ByVal statusText As String)
If InvokeRequired Then
Invoke(Sub() LblStatus.Text = statusText)
Else
LblStatus.Text = statusText
End If
End Sub
I understand this code but I do have trouble understanding how I can use this code or something like it for ListViewMain.BeginUpdate, ListViewMain.EndUpdate and ListViewMain.Items.Add.
Can someone guide me in the right direction?

Firstly, your delegate type is useless because you're not using it. You're using a Lambda expression to create a delegate so your DelegateUpdateStatus type is pointless. Secondly, you should be recalling the same method in the If block and then doing the actual work only once, in the Else block:
Private Sub UpdateStatus(ByVal statusText As String)
If InvokeRequired Then
Invoke(Sub() UpdateStatus(statusText))
Else
LblStatus.Text = statusText
End If
End Sub
The actual work to be done on the UI thread is done only in the Else block, so you can do whatever you want there, including adding items to a ListView, e.g.
Private Sub AddListViewItems(items As IEnumerable(Of ListViewItem))
If InvokeRequired Then
Invoke(Sub() AddListViewItems(items))
Else
ListViewMain.BeginUpdate()
For Each item in items
ListViewMain.Items.Add(item)
Next
ListViewMain.EndUpdate()
End If
End Sub

Related

vb.net delegate and invoke - Multithread

I am trying to understand how delegates and invoke work.
So I build a form, with a label and a button.
When someone clicks on the Button, the Text changes to "Stop" and a counter starts counting up. This counter should be displayed on a Label. This is my code:
Public Class Form1
Private t1 As Thread
Private sek_ As Integer = 0
Private Sub btn_read_Click(sender As Object, e As EventArgs) Handles btn_read.Click
If btn_read.Text = "Read" Then
btn_read.Text = "Stop"
t1 = New Thread(AddressOf stopw_)
t1.Start()
Else
lbl_stopw_.Text = ""
btn_read.Text = "Read"
End If
End Sub
Private Delegate Sub stopw_D()
Private Sub stopw_()
Do While btn_read.Text = "Stop"
sek_ = sek_ + 1
If lbl_stopw_.InvokeRequired Then
lbl_stopw_.Invoke(New stopw_D(AddressOf stopw_))
Else
lbl_stopw_.Text = sek_
End If
Thread.Sleep(1000)
Loop
sek_ = 0
If t1.IsAlive Then t1.Abort()
End Sub
End Class
If I start debugging, the form still freezes and the label does not get updated. If I delete all the delegate and invoke stuff and use Me.CheckForIllegalCrossThreadCalls = False Its working.
What am I doing wrong?
Control.Invoke() is used to execute a method on the same thread that the control was created on. Since code can only be executed one line at a time in each thread, executing a method on the control's thread will result in the method being "queued" until the other code in that thread, prior to the method, has completed. This makes it thread-safe as there will be no concurrency issues.
A Delegate is simply a class holding the pointer to a method. It exists so that you can use methods as if they were ordinary objects (in this case you pass it to a function). The AddressOf operator is a quick way of creating a delegate.
Now, you have a few issues in your code. First of all, you should not try to access or modify ANY UI element from a background thread. Whenever you want modify or check a control you must always invoke.
More specifically, I'm talking about your While-loop:
'You can't check the button here without invoking.
Do While btn_read.Text = "Stop"
It is better if you create a Boolean variable that indicates when the thread should run.
Private t1 As Thread
Private sek_ As Integer = 0
Private ThreadActive As Boolean = False
Set ThreadActive to True before you start the thead, then in your thread's While-loop check:
Do While ThreadActive
Now, there is another issue. Your UI freezes because of this:
If lbl_stopw_.InvokeRequired Then
lbl_stopw_.Invoke(New stopw_D(AddressOf stopw_))
NEVER invoke the the same method which the thread runs on! Doing so starts the processing all over again, but on the UI thread. Your loop makes the UI thread completely busy, which is why it doesn't redraw itself.
So when you are to update something, always invoke a seperate method. If you target .NET 4.0 or higher you can use lambda expressions for a quick, inline delegate:
If lbl_stopw_.InvokeRequired Then
lbl_stopw_.Invoke( _
Sub()
lbl_stopw_.Text = sek_
End Sub)
Else
lbl_stopw_.Text = sek_
End If
However if you are targeting .NET 3.5 or lower you have to stick to the normal way of using delegates:
'Outside your thread.
Private Delegate Sub UpdateLabelDelegate(ByVal Text As String)
Private Sub UpdateLabel(ByVal Text As String)
lbl_stopw_.Text = Text
End Sub
'In your thread.
If lbl_stopw_.InvokeRequired Then
lbl_stopw_.Invoke(New UpdateLabelDelegate(AddressOf UpdateLabel), sek_)
Else
UpdateLabel(sek_)
End If
Alternatively, in order to minimize the amount of code you have to write you can create an extension method to do the invoking for you:
Imports System.Runtime.CompilerServices
Public Module Extensions
<Extension()> _
Public Sub InvokeIfRequired(ByVal Control As Control, ByVal Method As [Delegate], ByVal ParamArray Parameters As Object())
If Parameters Is Nothing OrElse _
Parameters.Length = 0 Then Parameters = Nothing 'If Parameters is null or has a length of zero then no parameters should be passed.
If Control.InvokeRequired = True Then
Control.Invoke(Method, Parameters)
Else
Method.DynamicInvoke(Parameters)
End If
End Sub
End Module
Usage, .NET 4.0 or higher:
lbl_stopw_.InvokeIfRequired( _
Sub()
lbl_stopw_.Text = sek_
End Sub)
Usage, .NET 3.5 or lower:
lbl_stopw_.InvokeIfRequired(New UpdateLabelDelegate(AddressOf UpdateLabel), sek_)
When using this extension method you don't need to write InvokeRequired checks everywhere:
Do While btn_read.Text = "Stop"
sek_ = sek_ + 1
lbl_stopw_.InvokeIfRequired(New UpdateLabelDelegate(AddressOf UpdateLabel), sek_)
Thread.Sleep(1000)
Loop
And finally, this is just unnecessary:
If t1.IsAlive Then t1.Abort()
The thread will always be alive when it reaches that If-statement since it hasn't exited the stopw_ method yet. But once the thread has exited the method it will end normally, so there's no reason to call Abort().
The answer got a bit long, but I hope it to be helpful!

Multithreading doesn't work

I'm making a simple multithreading program to explain the working of threading. I want two counters counting on the same time but it doesn't work.
It only works if I use: CheckForIllegalCrossThreadCalls = False. But, I want to program in a proper way.
Code:
Dim Thread1 As System.Threading.Thread
Dim Thread2 As System.Threading.Thread
Private Delegate Sub SetTeller1()
Private Sub teller1()
If teller1Label.InvokeRequired Then
Invoke(New SetTeller1(AddressOf teller1))
Else
For i As Integer = 0 To 1000
teller1Label.Text = i
Refresh()
Next
End If
End Sub
Delegate Sub SetTeller2()
Private Sub teller2()
If teller2Label.InvokeRequired Then
Invoke(New SetTeller2(AddressOf teller2))
Else
For i As Integer = 0 To 1000
teller2Label.Text = i
Refresh()
Next
End If
End Sub
Private Sub teller1Button_Click(sender As Object, e As EventArgs) Handles teller1Button.Click
Thread1 = New Threading.Thread(AddressOf teller1)
Thread1.Start()
End Sub
Private Sub teller2Button_Click(sender As Object, e As EventArgs) Handles teller2Button.Click
Thread2 = New Threading.Thread(AddressOf teller2)
Thread2.Start()
End Sub
The multithreading works perfectly, but you are not utilizing it. The only thing you're currently doing in the background thread is calling Invoke, which means that your thread will exit within a few milliseconds and then be discarded.
Once you call Invoke the execution of the teller1 or teller2 method is moved to the UI thread, meaning it will block the UI until its execution is finished. You should only invoke when you are to update the UI, and perform all iterations in the background thread.
Here's an example of how you can do it more properly:
Delegate Sub SetTeller1(ByVal Text As String)
Private Sub teller1()
For i As Integer = 0 To 1000
SetTeller1Text(i)
Next
End Sub
Private Sub SetTeller1Text(ByVal Text As String)
If Me.InvokeRequired Then
Me.Invoke(New SetTeller1(AddressOf SetTeller1Text), Text)
Else
teller1Label.Text = Text
Me.Refresh()
End If
End Sub
For improved readability I changed for example Invoke(...) to Me.Invoke(...).
Also I'm not sure why you're calling Refresh() as it isn't necessary and will just cause extra redrawing of the entire container (guessing this is a form).

vb.net 2010 async report not working in GUI

I know this is going to be virtually impossible to answer without me posting code, so I'll try to give some examples to aid this but...
I have a written dll which does some processing. I have it async tasks which report back status messages, such as the thing it's currently working on. There are multiple threads running each processing a different thing.
Now... when I use my DLL in a console app, the status.report("what I'm doing") works fine. I have a method in my console app with a Console.Writeline(text) which works great.
However... when I use the SAME dll in a gui form, and use the SAME methods from the console within the form to run the SAME processes with the SAME data, the SAME method that works perfectly writing the line to the console is NOT triggered and NO report is even processed by the gui.
Example.
console app:
Imports myDLL
Module Module1
Sub Main
SAE(paramaters).wait()
End Sub
Private Async Function SAE(parameters) as Task
Dim progress_indicator As Progress(Of Integer) = New Progress(Of Integer)(AddressOf DisplayProgress)
Dim progress_text As Progress(Of String) = New Progress(Of String)(AddressOf textProgress)
Dim complete As Object = Nothing
complete = Await Task.Run(Function() MyDLL.Process1(other parameters, progress_indicator, progress_text))
End Function
Private Sub DisplayProgress(ByVal percentage As Decimal)
Console.WriteLine("percentage " + Format(percentage, "0.00"))
End Sub
Private Sub textProgress(ByVal text As String)
Console.WriteLine("sub - reporting: " + text)
End Sub
End Module
Public Class myDLL
Public Function SettleAll(other paramaters, progress_indicator As IProgress(Of Integer), status As IProgress(Of String)) As Boolean
Dim aThread As Thread
aThread = New Thread(Sub() _OtherProcess(other parameters, progress_indicator, status))
aThread.Start()
System.Threading.Thread.Sleep(10)
aThread.Join
End Function
Private Sub _OtherProcess(other parameters, progress_indicator, status))
Loop
'Do Some stuff...
status.Report("Report back this it's working on this, that or the other")
progress_indicator.Report(SomePercentageProgressVariable))
End Loop
End Function
End Class
Now... when I use this, I get messages in the console window as I expect. However... in the gui... when I copy the SAE method and put the Sub Main code into a button click like this:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
SAE(paramaters).wait()
End Sub
... and I change the following methods:
Private Sub DisplayProgress(ByVal percentage As Decimal)
Debug.Print("percentage " + Format(percentage, "0.00"))
End Sub
Private Sub textProgress(ByVal text As String)
TextBox1.AppendText(text)
Debug.Print("sub - reporting: " + text)
End Sub
NOTHING at all happens...
The DLL is doing the processing, but there's no reporting.
Think I solved it.
If I change the button on_click method to an Async method llike this:
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Await SAE(paramaters)
End Sub
It seems to work

Vb.Net - Invoke in Task

so basically I want my application to run a method that is quite cpu intensive and therewhile it should constantly display status information on a different form. To prevent this status form from freezing, I thought it would be a good idea to outsource the code into a new thread.
First I tried to use basic threadding and invoking the richtextbox control which should display status messages. - Problem is - I need to know when the Thread is finished to carry on with my main thread. Obviously I cannot simply run a loop in my main thread that keeps checking if the process is finished, cause that would make my GUI freeze, too.
So I did a little bit research and found out about Tasks.
That's how it looks:
Dim z as new complexProcessClass
Dim taskA = task.Factory.StartNew(Sub() z.start())
taskA.Wait()
If taskA.IsCompleted Then
MsgBox("finished")
End If
And whenever the process reports a status I use this:
Public Class complexProcessClass
dim statusWindow as statusForm
Public Sub start()
statusWindow = new statusForm
'complex code here
reportStatus("bla")
'complex code here
reportStatus("blabla")
'complex code here
End Sub
Private Delegate Sub UpdateTextHandler(ByVal Text As String)
Private Sub reportStatus(Byval s as String)
If z.RichTextBox1.InvokeRequired Then
Try
z.RichTextBox1.Invoke(New UpdateTextHandler(AddressOf xform.RichTextBox1.AppendText), s)
Catch ex As Exception
MsgBox(ex.ToString())
End Try
Else
z.RichTextBox1.AppendText(s)
End If
End Sub
But it just keeps freezing on the invoke call - no error message - nothing?!
Can anybody tell me the correct way to do this? - and please no backgroundworker solution ;)
thanks in advance
Take a look at the BackgroundWorker class. This post should get you started.
Another approach is to create delegates and call the thread asynchronously and implement an update function to catch when the work is done.
Create a class with the work code as a function
At the top of the class create/add Delegate Function Handler
Inside of your form add a handler to the new class with a call to your class method.
Create a callbackhandler method to receive the status of the thread performing the export functionality.Have the callbackhandler call an update ui function that checks if the thread is running from the UI or is another thread. (Me.InvokeRequired checks this)
Inside of the Form btn click event call the method using
the targetHandler call.
The below code is what the form code would look like.
Public Class Form1
Private targetHandler As ClassName.Handler = AddressOf objNewClass.somework
Private callbackHandler As AsyncCallback _
= AddressOf MyCallbackMethod
Sub MyCallbackMethod(ByVal ar As IAsyncResult)
'*** this code fires at completion of each asynchronous method call
Try
Dim retval As Boolean = targetHandler.EndInvoke(ar)
If retval = True Then
Console.Write(retval)
End If
UpdateUI("Task complete")
Catch ex As Exception
Dim msg As String
msg = "Error: " & ex.Message
UpdateUI(msg)
End Try
End Sub
Sub UpdateUI(ByVal statusMessage As String)
If Me.InvokeRequired Then
Dim handler As New UpdateUIHandler(AddressOf UpdateUI_Impl)
Dim args() As Object = {statusMessage}
Me.BeginInvoke(handler, args)
Else
UpdateUI_Impl(statusMessage)
End If
End Sub
Delegate Sub UpdateUIHandler(ByVal statusMessage As String)
Sub UpdateUI_Impl(ByVal statusMessage As String)
Me.sbMain.Panels("Status").Text = statusMessage
End Sub
'Call to your worker thread
Private Sub btn_Click() Handles Button1.Click
Dim result As IAsyncResult =targetHandler.BeginInvoke(callbackHandler,Nothing)
End Sub
End Class
You have a deadlock situation where taskA.Wait() is blocking the UI thread, and the Invoke() call inside taskA is waiting for the UI thread to finish what it's doing. Which is waiting until it's done waiting. Which is never.
I'm not entirely sure, but try this:
Dim taskA = task.Factory.StartNew(Sub() z.start()).ConfigureAwait(False)

Cross Thread invoke from class ? Confused - vb.net

maybe I am being stooped... but the fact is that I am a bit of a n00b concerning threading...
I am making use of a serial port in a class. I am raising an event from that class to my form calling the class. Event contains data received...
I wish to simply populate a textbox from the raised event.
Now I am not specifically creating a seperate thread, but I get the normal crossthreading error when trying to update my textbox on the UI, so my assumption is that the serial port and its internal methods probably creates its own threads...
Regardless, I am a bit confused as to how to properly implement an invoke, from my main form, pointing to the thread in the instantiated class...
I hope this makes sense...
Dim WithEvents tmpRS232 As New clsRS232
Private Sub but_txt_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles but_txt.Click
tmpRS232.Set_com_port("COM8", 38400)
tmpRS232.Transmit_data(txt_tx.Text)
End Sub
Private Sub tmprs232_rx_data_returned(ByVal str_data As String) Handles tmpRS232.rx_data_returned
txt_rx.Text = str_data 'Cross threading error
MsgBox(str_data) 'Fires without errors
End Sub
Can someone please provide a code example based on this code?
thanks.
You are correct, the issue here is that you are attempting to update a UI element from a non-UI thread (in this case the serial port handler). What you need to do is check if the InvokeRequired flag is set on the control that you are trying to access from the callback. If so that means that you need to marshall your call to the UI thread. You can achieve this by using either Invoke or BeginInvoke from System.Windows.Forms.Control.
Private Delegate Sub SetRxTextCallback(ByVal [text] As String)
Private Sub SetRxText(ByVal [text] As String)
txt_rx.Text = [text]
End Sub
Private Sub tmprs232_rx_data_returned(ByVal str_data As String) Handles tmpRS232.rx_data_returned
If (txt_rx.InvokeRequired) Then
Dim d As New SetRxTextCallback(AddressOf Me.SetRxText)
Me.BeginInvoke(d, New Object() {[str_data]})
End If
'txt_rx.Text = str_data 'Cross threading error
'MsgBox(str_data) 'Fires without errors
End Sub
Here's a link to the MSDN documentation that explains it in detail.
Or simply...
Private Sub tmprs232_rx_data_returned(ByVal str_data As String) Handles tmpRS232.rx_data_returned
If InvokeRequired Then
Invoke(Sub()txt_rx.Text = str_data)
Else
txt_rx.Text = str_data
End If
End Sub