Threadding: Update UI without blocking the thread? - vb.net

I have written a server program that does a lot of jobs in threads simultaneously.
In those threads, I have to update a ListView with status information, but as it is right now using invoke, the thread waits for the UI to finish updating the ListView.
Any good advice to how I can send the status to the ListView and continue the thread while ListView finish updating?
Here's my code...
Public Delegate Sub InfoDelegate(status As String)
Public Sub Info(status As String)
If Me.InvokeRequired Then
Dim d As New InfoDelegate(AddressOf Info)
Me.Invoke(d, status)
Else
Dim item As New ListViewItem With {
.Text = status}
With lv
.BeginUpdate()
.Items.Insert(0, item)
If .Items.Count > 500 Then
For i As Integer = Me.lv.Items.Count - 1 To 500 Step -1
Me.lv.Items.RemoveAt(i)
Next
End If
.EndUpdate()
End With
End If
End Sub

You can call Control.BeginInvoke() to invoke the method asynchronously. However that call needs to be followed by a EndInvoke() call, or else you will get memory and/or thread leaks.
In the .NET Framework versions 4.0 and up you can utilize lambda expressions to pass the IAsyncResult returned from the BeginInvoke call to the lambda expression itself. Thus, you can call EndInvoke without having it block since by the time that it is called the asynchronous operation is already finished.
Here's an example:
Dim iar As IAsyncResult = _
Me.BeginInvoke(Sub()
Info("Status here") 'Calling your Info() method.
Me.EndInvoke(iar)
End Sub)

Related

vb.net Async I just don't get it

Public Function PiesTableTest(compairFile As String, version1 As String, Optional silent As Boolean = False) As Boolean
Dim dpgs As New frmDetailProgress
Dim retturn As Boolean
PiesThreadedTableTest(compairFile, version1, silent, dpgs)
End Function
Async Function PiesThreadedTableTest(compairFile As String, version1 As String, silent As Boolean, dpgs As frmDetailProgress) As Task(Of Boolean)
Dim ctl() As xmlControlAry
Dim xmlDoc As XElement
Dim xmlNodes As IEnumerable(Of XElement)
Dim notfound(0) As String
version = version1
nodeErrors = False
If Not silent Then
dpgs.lblTital.Text = "Pies Configuration Check"
dpgs.add("Pies Version = " & version)
dpgs.add("Loading Config Data....")
dpgs.Show()
End If
' load configuration data
GetPiesControl(ctl, version)
' load test xml file
xmlDoc = XElement.Load(compairFile)
xmlNodes = xmlDoc.Elements()
For Each ele As XElement In xmlNodes
NodeDrill("", ele, ctl, dpgs, notfound, silent)
Next
If nodeErrors And Not silent Then
dpgs.add("Testing done with Errors!!!", "R")
Else
dpgs.add("Testing Done NO ERRORS!", "G")
End If
Application.DoEvents()
If silent Then
dpgs.Dispose()
End If
'PiesThreadedTableTest = Not nodeErrors
If nodeErrors Then
Return False
Else
Return True
End If
End Function
I am trying to understand multi threading. frmDetailProgress is a "please wait " kind of form. and i have a animated gif on it. Plus it has a check box to close automatically after completion. Well the form is frozen till the process is done. I am trying to get the piesthreadedtabletest to run in another thread. I have read allot on this but i just don't understand the concept. I don't understand the await function enough to make this work. i get that await is designed to stop processing until something happens. But i want that form freed up to work. I get an error saying that the function will run synchronously unless i have an await - Why?
I got it working. It was a lack of understanding and i probably still need to learn more. I hope this will help someone in the future.
i created a class to call functions in the other class running in the second thread.
imports system.threading
public sub callThreadedProcedure()
dim tp as system.threading.thread ' this will be for the object running in the other thread
dim objectToRun as myclass ' this is the object you want to run in the thread
'this gets the object and puts it into the new thread
tp = new thread(sub() objectToRun.FunctionToRun(<put your parameters here if any>))
' start execution of the object in a new thread.
tp.start()
' that will get it to run in a separate thread. It works, there might be a better way
' and might not work in all situations, but for now it fixed my problem.
end sub
if you are trying to run functions in the original thread you need to pass a
reference to that object to the one in the second thread. you must then use invoke to run a function or sub from the second thread.
Invoke(sub() obj.function(<parameters>))
thanks Idle_mind invoked worked like it should.
I appreciate all that helped me along.

Dispatcher, Tasks and cancellation issues

I am writing a library to automate internet explorer. The library sets up its own message loop by starting a thread and using Dispatcher.Run. The reason for this is to keep internet explorer and mshtml on the same thread.
I then use the following in the library.
Private ExDispatcher As Dispatcher
Private Success As Boolean
Private EXThreadWaiter As Threading.AutoResetEvent
Public Async Function ClickAndWaitForNewPageAsync() As Task(Of Boolean)
Return Await Task.Run(Of Boolean)(Function()
ExDispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, Sub() startClickAndWaitForNewPage())
EXThreadWaiter.WaitOne()
Debug.Print("Completed with: " + Success.ToString)
Return Success
End Function)
End Function
Private Sub startClickAndWaitForNewPage()
'do a lot of stuff and wait for internet explorer
'if we are happy and all good set Success = True
'now let the thread in the task go
EXThreadWaiter.Set()
End Sub
ClickAndWaitForNewPageAsync starts a task which is on a new thread, it immediately then uses my message loop to call startClickAndWaitForNewPage and all the Internet Explorer stuff is done. When everything is ok the variable Success is set to a value and EXThreadWaiter.Set() is called which releases the task thread and the result is returned.
A client of this library therefore uses it as shown below.
Private Async Sub someFunction()
Dim res As Boolean = Await ClickAndWaitForNewPageAsync()
If res Then
'continue on with code
Else
'do nothing
End If
End Sub
The issue I have with this is how to shutdown or stop my library which needs to happen for various reasons. I currently have this method in the same class as ClickAndWaitForNewPageAsync
Public Sub StopIt()
Success = False
EXThreadWaiter.Set()
End Sub
This method sets Success to False and lets the task continue. This mostly works fine but it is possible that the following happens.
startClickAndWaitForNewPage sets Success to True
EXThreadWaiter.Set() is called
Debug.Print("Completed with: " + Success.ToString) indicates Success is True
StopIt() runs setting Success = False
someFunction in the client code receives Success = True
This means that in someFunction it continues with its code thinking everything is ok but in fact it is not.
How can I prevent this from happening?
By the way the Cancellation Token etc i do not think will help, but I'm happy to hear differently.
What I previously used
I previously did not use Tasks. I would simply use the Dispatcher begininvoke which would mean everything is done on the right thread and the client would have to subscribe to an event that would fire when everything was done successfully. This felt untidy to me and thus I am trying this new method.

Assigning a reference type object a value on a secondary thread, and then working with this object from my primary thread

I am trying to create a variable which is of type MyReferenceTypeObject and of value null on thread one, use a delegate to make this thread equal to a new instance of MyReferenceTypeObject on thread two, and then access members of MyReferenceTypeObject back on thread one (in the delegates callback method).
My code is below:
Module Module1
Delegate Sub EditReferenceTypePropertyDelegate(ByVal referenceTypeObject As MyReferenceTypeObject, ByVal numberToChangeTo As Integer)
Sub Main()
Dim editReferenceDelegate = New EditReferenceTypePropertyDelegate(AddressOf EditReferenceTypeProperty)
Dim newReferenceTypeObject As MyReferenceTypeObject
editReferenceDelegate.BeginInvoke(newReferenceTypeObject, 2, AddressOf EditReferenceCallback, newReferenceTypeObject)
Console.ReadLine()
End Sub
Sub EditReferenceTypeProperty(ByVal referenceTypeObject As MyReferenceTypeObject, ByVal numberToChangeTo As Integer)
referenceTypeObject = New MyReferenceTypeObject()
referenceTypeObject.i = numberToChangeTo
End Sub
Sub EditReferenceCallback(ByVal e As IAsyncResult)
Dim referenceObject = DirectCast(e.AsyncState, MyReferenceTypeObject)
Console.WriteLine(referenceObject)
End Sub
End Module
Class MyReferenceTypeObject
Public Property i As Integer
End Class
However, newReferenceTypeObject comes into my callback method as null. I think I understand why, but the problem is that I need to pull some data from a database which I then need to pass into the constructor of newReferenceTypeObject, this takes a couple of seconds, and I don't want to lock up my UI while this is happening. I want to create a field of type MyReferenceTypeObject on thread one, instantiate this on thread two (after I have pulled the data of the database to pass into the constructor) and then work with members of the object back on thread one once the instantiation is complete.
Is this possible? I am using VB.Net with .Net 4.0 on Visual Studio 2012.
If you want to keep the GUI responsive during a long running action, I'd consider using the Task<> library (Comes with .NET 4.0). Here's a quick example.
Sub Main()
Dim instantiateTask = New Task(Of MyReferenceTypeObject)(Function()
' Call your database to pull the instantiation data.
Return New MyReferenceTypeObject With {.i = 2}
End Function)
instantiateTask.Start() ' Start the task -> invokes a ThreadPool.Thread to do the work.
instantiateTask.ContinueWith(Sub(x)
Console.WriteLine(x.Result.I)
End Sub, TaskScheduler.FromCurrentSynchronizationContext())
End Sub
.Wait blocks the GUI thread. However, you could use ContinueWith which is async and therefor nonblocking. Also you need to provide the TaskScheduler ( TaskScheduler.FromCurrentSynchronizationContext ) from the GUI thread to prevent cross-thread exceptions in case you want to update the UI from within the async method.

Newbie: Invalid cross thread access with using busyindicator (silverlight and wcf service)

I have a problem with the busyindicator control of silverlight.
I have a datagrid (datagrid1) with its source set to a wcf service (client).
I would like to use the busyindicator control (bi) of silvelright toolkit when the datagrid loads itself.
But I have an "Invalid cross thread access" when I use "ThreadPool".
Sub LoadGrid()
Dim caisse As Integer = ddl_caisse.SelectedValue
Dim env As Integer = ddl_env.SelectedValue
bi.IsBusy = True
ThreadPool.QueueUserWorkItem(Sub(state)
AddHandler client.Get_PosteSPTCompleted, AddressOf client_Get_PosteSPTCompleted
client.Get_PosteSPTAsync(caisse, env)
Dispatcher.BeginInvoke(Sub()
bi.IsBusy = False
End Sub)
End Sub)
End Sub
Private Sub client_Get_PosteSPTCompleted(sender As Object, e As ServiceReference1.Get_PosteSPTCompletedEventArgs)
DataGrid1.ItemsSource = e.Result ' Here, Invalid cross thread access
End Sub
I know that the datagrid control doesn't exist in the "new thread", but how have to I make to avoid this error?
Thank you.
William
First point: Why are you using the ThreadPool?
Using a ThreadPool would be a good idea if your service was synchronous, but your WCF service is asynchronous: it won't block your UI thread after being called using Get_PosteSPTAsync.
Second point: there seems to be an issue with your IsBusy property. You're first setting it to true, then you starts an operation asynchronously, then you set it to false immediately. You should set it to false in the Completed handler.
Third point: the client_Get_PosteSPTCompleted handler won't be executed in the same thread as the UI thread (even if you don't use the ThreadPool). That's why you'll have to use the dispatcher here so force using the UI thread.
Your DataGrid1.ItemsSource = e.Result must be modified to:
Dispatcher.BeginInvoke(Sub()
DataGrid1.ItemsSource = e.Result ' Fixes the UI thread issue
bi.IsBusy = False ' Sets busy as false AFTER completion (see point 2)
End Sub)
(note sure about the VB.Net syntax, but that's the idea)
Last point: I don't know about the client object lifetime, but you're adding a new handler to the Get_PosteSPTCompleted each time you call LoadGrid. Maybe you could think of detaching handlers after use.

Why am I getting a "Cross-thread operation not valid exception" in the callback method when using AsyncCallback with BeginInvoke?

I have a windows form that gets data from a scale via the serial port. Since I need the operator to be able to cancel the process I do the get data process on a second thread.
The data collection process gets multiple readings from the scale one at a time. The form has a label that needs to be updated with information specific to each reading.
I call the method to get the data from the scale with this code.
Dim ad As New readALine(AddressOf thisScale.readALine)
Dim ac As New AsyncCallback(AddressOf Me.GetDataCallback)
Dim iar As IAsyncResult = ad.BeginInvoke("", ac, ad)
The delegate for the readALine method is defined in the UI code.
Delegate Function readALine(ByVal toSend As String) As String
The GetDataCallback method:
Private Sub GetDataCallback(ByVal ia As IAsyncResult)
.
.
.
lblInstructions.Text = _thisMeasure.dt.Rows(_currRow - 1).Item("cnt") & ":"
lblInstructions.Refresh()
.
.
.
End Sub
I get the exception on the "lblInstructions.Text =" statement.
I thought the GetDataCallback method was part of the UI thread so don't understand why I'm getting the exception. I know this could probably be rewritten using a BackgroundWorker and it's appropriate events but for now would like to understand why this isn't working as expected.
The application was written originally in VS2003 and just recently upgraded to VS2008.
Any insight will be appreciated.
Thanks,
Dave
The problem is a confusion over BeginInvoke. Calling Control.BeginInvoke marshals a delegate call to the UI thread. Calling BeginInvoke on a delegate causes it to be executed on a thread pool thread - and any callback you provide is executed on the same (thread pool) thread. The latter is what you're doing, which is why GetDataCallback is being executed on a thread pool thread, not the UI thread..
So, within GetDataCallback you need to call Control.Invoke or Control.BeginInvoke to marshal back to the UI thread.
One point to note: I very rarely use Control.InvokeRequired these days - it's simpler to unconditionally call Invoke/BeginInvoke; the performance difference isn't usually enough to make up for the benefit in readability, in my experience.
user-interface controls can only be updated by the thread that created them
try
yourForm.BeginInvoke
instead; that should marshall the call to the correct thread
In .NET 1.1 it was possible to perform these cross-thread operations, even though they weren't safe.
In .NET 2.0 the exception you mention is thrown, when you try to perform cross-thread operations such as this, which means that you are performing the UI operations on a non-UI thread, even though you thought you weren't.
Try using the Me.InvokeRequired method to determine whether you are currently on the UI thread. E.g. you could define a method to perform the necessary tasks:
Protected Sub PerformUIOperations()
If Me.InvokeRequired Then
'We are currently on a non-UI thread. Invoke this method on the UI thread:
Me.Invoke(New MethodInvoker(AddressOf Me.PerformUIOperations))
Return
End If
'Perform UI operations when we know it is safe:
lblInstructions.Text = "blabla"
End Sub
The PerformUIOperations method can then be called from any non-UI thread, since it takes care of performing the tasks on the correct thread itself.
Of course, if you need to pass parameters to the PerformUIOperations method (such as information regarding the ongoing operation) you'll have to define a delegate to match the PerformUIOperations method signature and use this instead of the MethodInvoker.
Jon,
I have the following in another section of the code:
Delegate Sub setValueCallback(ByVal row As Integer, ByVal value As Decimal)
Public Sub setValue(ByVal row As Integer, ByVal value As Decimal)
If Me.Controls.Item(_tbIndex(row - 1)).InvokeRequired Then
Dim d As New setValueCallback(AddressOf setValue)
Me.Invoke(d, New Object() {row, value})
Else
Dim tb As TextBox = Me.Controls.Item(_tbIndex(row - 1))
tb.Text = value
_dt.Rows(tb.Tag).Item(1) = value
End If
End Sub
How would this be rewritten to not use .InvokeRequired?
Dave, maybe this is the solution you are looking for:
Dim ad As New readALine(AddressOf thisScale.readALine)
Dim ac As New AsyncCallback(AddressOf Me.GetDataCallback)
Dim iar As IAsyncResult = ad.BeginInvoke("", ac, ad)
Delegate Function readALine(ByVal toSend As String) As String
Private Sub GetDataCallback(ByVal ia As IAsyncResult)
If lblInstructions.InvokeRequired Then
lblInstructions.Invoke(New AsyncCallback(AddressOf GetDataCallback), New Object() {ia})
Else
.
.
lblInstructions.Text = _thisMeasure.dt.Rows(_currRow - 1).Item("cnt") & ":"
lblInstructions.Refresh()
.
.
End If
End Sub