parallel event update UI - vb.net

I have a class written in C#. In it I want to run a certain function in parallel on a list. After it completes on each item I would like to update a progress bar. However, I get very odd behavior from my program. It executes the event and reaches my sub but never proceeds to actually execute any code. Instead it just freezes. (I've mixed vb.net and c#. It will be rewritten at some point)
so in my windows form I call
progressBar.Visible = True
progressBar.Value = 0
progressBar.Maximum = dataGrid.SelectedRows.Count
AddHandler quoteManager.refreshStarted, AddressOf progressBarCounter
quoteManager.refreshAllAsync(list)
and the event is simply
Private Sub progressBarCounter()
Me.Invoke(Sub()
If progressBar.Value = progressBar.Maximum Then
progressBar.Visible = False
Else
progressBar.Value += 1
End If
End Sub)
End Sub
and in the quote manager class I have this defined.
public event Action refreshStarted;
public void refreshAllAsync(List<BindableQuote> bindableQuotes)
{
bindableQuotes.AsParallel()
.WithDegreeOfParallelism(10)
.ForAll((quote) => {
quote.refreshAll();
if (refreshStarted != null) { refreshStarted(); }
});
}
So for some reason I get it to enter progressBarCounter on each item in the list but it never exists. Instead it just keeps the form frozen.

I am not exactly sure this is what is happening, but it looks like progressBarCounter is blocking because you are calling Invoke. Should you be using BeginInvoke instead? Using BeginInvoke might solve the deadlock issue. See this post: What's the difference between Invoke() and BeginInvoke()

What appears to be happening here is that you access UI objects from multiple threads.
That's not supported. You'll have to run this code on a worker thread, and let it somehow accumulate progress, and send messages back to the UI thread. The BackgroundWorker class can help you implement the marshalling back to the UI thread.

Related

Task's ConfigureAwait(False) not working

I've read a lot of tutorials with regards to await/async and i've understood that when it comes to await keyword it will go back to the main context then go back to await (when it's finished) and then continue further if there's something after away within async method. I also read that there is something like ConfigureAwait(False) which simply means that with opposite to i've written before when it comes to await it will not go back to main context at this point but will stay here and wll wait on await to be finished and then continue in async method after that will go back to main cotext. So diffrence is it will not go back to main context but wait on await. Correct if i am wrong. Based on that knowledge in first example i should see following result:
After DoStafAsync
Done running the long operation asynchronously.
but when i just simply add ConfigureAwait(False) it should be:
Done running the long operation asynchronously.
After DoStafAsync
as it will not go back to context but will wait and after async method finishes then will go back. Somehow i see the result as in first output. What i am missing?
Public Class Form1
Sub LongOperation()
Threading.Thread.Sleep(5000)
End Sub
Private Async Function DoStafAsync() As Task
lblStatus.Text = "Running a long operation asynchronously... (UI thread should be fully responsive)"
Await Task.Run(Sub()
LongOperation()
End Sub).ConfigureAwait(False)
lblStatus.Text = "Done running the long operation asynchronously."
End Function
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
dfg
lblStatus.Text = "After DoStafAsync"
End Sub
Async Sub Dfg
Await DoStafAsync
End Sub
End Class
Additional question: Some people claims that tasks are not creating
threads but working on same ui thread. But there are some people
claiming that they are and there are also people claiming that
sometimes tasks creating threads and sometimes not. What is true here?
i've understood that when it comes to await keyword it will go back to the main context then go back to await (when it's finished) and then continue further if there's something after away within async method.
I don't think you're using the term "context" the way it's normally used when explaining async code. Check out my async intro blog post, which explains exactly what the context is and how it's used by await.
In particular, "context" does not mean "method or function". It does not determine how or when the code returns to another part of the code.
You may also want to read my MSDN article on async best practices, which explains why you should avoid async void (except for event handlers). If you make Form1_Load an async void and get rid of dfg, then it works as you would expect:
Private Async Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Await DoStafAsync
lblStatus.Text = "After DoStafAsync"
End Sub
The reason it wasn't working before is because Form1_Load was not waiting for dfg (or DoStafAsync) to complete - it was just firing them off and then proceeding immediately to setting the label text. As I point out in my best practices article, one of the main problems with async void (async sub) is that the calling code cannot determine when it has completed (at least, not easily).

Threading in VB.net

I have a very basic doubt in vb.net threading.
I am having a function MyFunc1() which actually launches a form and asks for input from the user and returns a string. The return must be done only when user clicks a button called 'return' in the Form.
So I framed the function like this.
Public done as Boolean = true
Public str as String
Function MyFunc1() As String
Start Thread1 //launch UI as seperate thread
While done
End While //Infinite loop to hold the parent loop till done is made as false
return str
End Function
Function Thread1
//code to launch UI
End Function
Function onClickReturn //Function triggered when 'return' is pressed
str = EditText.text
done = false
End Function
The problem right now is Thread1 launches the UI but once the UI is launched Thread1 dies and so does the UI Panel.
Any ways to fix this?
I think what you are trying to do doesn't event need threads. Since your blocking the original thread anyways, there is really no need to create a second thread. Typically you just call
MyForm.ShowDialog()
That shows a modal dialog and will block the calling code at that line, allowing the UI to be displayed and used until the user dismisses it.
If you have a .Net thread object, you can block by calling Thread.Join.

Updating GUI items upon an event raised by a thread of another class VB.nET

How to update data in GUI with messages that are being received by a thread of another class ?
I have a class with a thread that receives data from a server. This thread raises an event whenever it gets a message from the server. This event is handled inside the Starter Class (main class which contains the GUI).
The event handler (say DisplayData() has to display the message received by the other class.
My code is like this
Class GUI
receiverObj = New Receiver()
Addhandler receiverObj.MessageAlert, Addressof DisplayData
...
...
Sub DisplayData()
Dim str As receiverObj.ReceiveData
lbEvents.Add.Items(str) ' lbEvents is a ListBox inside the GUI that displays messages from Receiver
End Sub
End Class
Class Receiver
Public Event MessageAlert()
Sub New ()
MyTcpClient = New TcpClient(hostIP, port)
MyTcpClient.GetStream.BeginRead(ReceiveData, 0, PacketSize, AddressOf ReceiveStream, Nothing)
End Sub
Public Sub ReceiveStream(ByVal ar As IAsyncResult)
Dim ByteCount As Integer
Try
ByteCount = MyTcpClient.GetStream.EndRead(ar)
Dim t As New Threading.Thread(Sub() RaiseEvent MessageAlert())
MyTcpClient.GetStream.BeginRead(ReceiveData, 0, PacketSize, AddressOf ReceiveStream, Nothing)
End Sub
End Class
The Window crashes or hangs and the listbox does not display data. Throws exception saying
Cross-thread operation not valid: Control xxx accessed from a thread other than the thread it was created on.
Can anybody suggest a way to fix this error ?
How to update data in GUI with messages that are being received by a thread of another class ?
Updates to GUI elements of a Windows application must take place on the thread that created the GUI.
To fix this, there's a method called Invoke that allows you to fire a delegate that can ensure control is passed to the GUI thread and perform the update you are attempting.
You need a few things to make this work:
A Delegate type , such as
Delegate Sub MyGUIUpdateDelegate()
A variable of the type of your delegate
Public myGUIUpdateDelegate as MyGUIUpdateDelegate
A method having a signature that matches the delegate and does the work:
Public Sub MyGuiEventHandler()
' Do work on proper GUI thread, via Control.Invoke,
' such as listbox population
If (Me.InvokeRequired) Then
Me.Invoke( myGUIUpdateDelegate)
Else
// do control specific work, we're on the GUI thread here
End If
End Sub
An assignment of the event handler to your delegate:
myGUIUpdateDelegate = New MyGuiUpdateDelegate(AddressOf myForm.MyGuiEventHandler)
A call to your updater method via Control.Invoke to the proper thread from the event thread (assuming
your form instance variable is named myForm):
myForm.Invoke(myForm.myGUIUpdateDelegate);
That's at least a framework that should help you get started. The idea is that the background thread that wants to induce the update should not (and in reality, cannot) make direct GUI updates. The proper way to initiate a context switch to the GUI thread is by calling the Invoke method to call the GUI updater on the proper GUI thread.
Additionally, if you need to pass parameters to your delegate, simply alter the signature of the Delegate you define to include the parameter(s), and modify the Invoke method to provide the arguments in the handler, and the 2nd argument to Invoke.
Hope this helps.

.NET End Invoke / Post Operation Completed

My question involves async operations in VB .NET.
Given the following:
Delegate WorkerDelegate(Byval asyncOp As AsyncOperation)
Public Sub StartWork()
Dim worker as new WorkerDelegate(AddressOf DoWork)
Dim asyncOp as AsyncOperation = AsyncOperationManager.CreateOperation(New Object)
// begin work on different thread
worker.BeginInvoke(asyncOp, Nothing, Nothing)
End Sub
Private Sub DoWork(Byval asyncOp as AsyncOperation)
// do stuff
// work finished, post
asyncOp.PostOperationCompleted(AddressOf OnDownloadFinished, Nothing)
End Sub
Private Sub OnDownloadFinished()
// Back on the main thread now
End Sub
Most resources I've read say that if you use BeginInvoke on a delegate you must call EndInvoke. In my example above I am using the PostOperationCompleted method to switch threads back and report the operation has finished.
Do I still need to get an IAsyncResult when I call worker.BeginInvoke and add worker.EndInvoke in the OnDownloadFinished method?
It's best practice to call EndInvoke, because that's when resources assigned by the AsyncResult are cleaned.
However, AFAIK the async result used by the asynchronous delegate does not use any resource if you don't access the WaitHandle property, so not calling the EndInvoke may have no impact.
In your scenario, you should consider using ThreadPool.QueueUserWorkItem.
In the example on MSDN, the equivalent of your OnDownloadFinished method looks like this:
// This method is invoked via the AsyncOperation object,
// so it is guaranteed to be executed on the correct thread.
private void CalculateCompleted(object operationState)
{
CalculatePrimeCompletedEventArgs e =
operationState as CalculatePrimeCompletedEventArgs;
OnCalculatePrimeCompleted(e);
}
It does not call EndInvoke(). So it's safe to assume that not calling EndInvoke() in the PostOperationCompleted handler is alright.

Windows Forms with async socket; no text output

Currently I am having a weird problem which I simply do not understand. I have a simple GUI, with one button & one richeditbox. I have an async socket running, I am receiving some data over the network which I want to print to the gui(richeditbox). The async socket is being started when the user hits the button. So when I receive the network data I call a function which prints the data, here how it looks like (in form1 class):
Public Sub AddText(ByVal text As String)
Try
Console.WriteLine(text)
RichTextBox1.AppendText(text)
RichTextBox1.AppendText(vbNewLine)
Catch e As Exception
Console.WriteLine(e.ToString())
End Try
End Sub
Then I simply do Form1.AddText(..) from my network class or a module (does it matter?). The problem is that nothing appears in the richeditbox, even though the AddText function is being called, no exceptions, no errors, simply nothing. I've looked thru it with the debugger, and "text" contained the data it had to print, but simply nothing appears.. Anyone have an idea?
If the socket is running on another thread (which, of course, it is because it's asynchronous), you may have to use InvokeRequired in order to get the RichTextBox to display the text. I had a similar issue with a listener on an asynchronous serial port listener.
I'm pretty sure David is right. Here's an example.
Delegate Sub AddTextDelegate(ByVal text as String)
Public Sub AddText(ByVal text as String)
If Me.InvokeRequired Then
Me.Invoke(new AddTextDelegate(AddressOf Me.AddText), new object() { text })
Else
Try
Console.WriteLine(text)
RichTextBox1.AppendText(text)
RichTextBox1.AppendText(vbNewLine)
Catch e as Exception
Console.WriteLine(e.ToString())
End Try
End If
End Sub
The deal is that controls have to be updated on the thread they were created on. It sounds like the AddText() routine is being called in the context of your async socket's thread. The AddText() routine will behave like a recursive function. The first time it's called, the InvokeRequired property will be true. This will cause it to be called again via the Invoke() call, which takes care of marshaling the data to the correct thread. The second time it's called, InvokeRequired will be false, and the control will be updated.
Fixed. I couldn't use Form1 to call the functions, because it's a type, its like a new var there with its own memory, since its a diff thread. So when I checked InvokeRequired, it said false because that Form1 belongs to that Thread, and thus no text was being displayed because I didn't even see the form. So just made a global var such as Public myForm As Form1 and assigned myForm to Form1 in Form1_Load.