I am trying to to implement a serial port interface that waits for the integer return value to be updated. For learning purposes, I have an Async function to return an integer from a process function that has a random time delay. I included the errors I see in the comments. The function seems to not return an Integer, and there is no GetAwaiter.
Private Sub btnSubMain_Click(sender As Object, e As EventArgs) Handles btnSubMain.Click
Dim Answer As Integer
Answer = SomeInteger() 'Value of type 'Task(of Integer)' cannot be converted to 'Integer'
End Sub
Public Async Function SomeInteger() As Task(Of Integer)
Dim exampleInteger As Integer = Await AwaitedprocessAsync() 'Await requires that type 'Integer"
' have a suitable GetAwaiter method
Return exampleInteger
End Function
Function AwaitedprocessAsync() As Integer
Dim timeout As Integer = CInt(Rnd() * 100)
Thread.Sleep(timeout)
Return 34 'return some test integer
End Function
I guess the reason you want to use Async / Await is to perform asynchronous processing without blocking the UI. But the way you have set it up looks like it will block the UI anyway. Here is an example which demonstrates how you can call a method without blocking, and with blocking
Private Async Sub btnSubMain_Click(sender As Object, e As EventArgs) Handles btnSubMain.Click
Dim answer As Integer
Try
btnSubMain.Enabled = False
' async call, UI continues to run
answer = Await SomeIntegerAsync()
Finally
btnSubMain.Enabled = True
End Try
MessageBox.Show(answer.ToString())
Try
btnSubMain.Enabled = False
' synchronous call, UI is blocked
answer = SomeInteger()
Finally
btnSubMain.Enabled = True
End Try
MessageBox.Show(answer.ToString())
End Sub
Function SomeIntegerAsync() As Task(Of Integer)
Return Task.Run(AddressOf SomeInteger)
End Function
Function SomeInteger() As Integer
Thread.Sleep(5000)
Return 34
End Function
The first thing to note is that the UI event handler is marked Async, so the awaiting is done in that method. It will be clear to most that your intent is to allow something to happen without blocking the UI. Since it is marked that way, it must contain Await. I have written two functions, one which returns a task and has Async in the name, and another which just returns the integer, without Async in the name.
I call both in the button click handler. The first call will allow the UI to run smoothly while calling the function. You can check that by moving the window around after clicking. The second calls the function directly and blocks the UI. This is a basic design you can follow.
A comment on your question notes you could use Task.Delay but I think sleeping the thread is a good way to simulate long running code which you don't want to block the UI so it makes sense as a demonstration.
Related
I am trying to update a label on my form after I receive a message from an async fired event from another class, and nothing is working so far.
Well, one thing that worked was adding a timer on the main thread that updates the label every 200ms with a public variable from the other class. But there must be an other way.
I tried to use the invoke method, but that didn't work either.
What am I missing/doing wrong?
edit: The function below is called with:
Await SubscribeToWebsocketEvents(creds)
The function:
Public Shared Async Function SubscribeToWebsocketEvents(ByVal creds As Credentials) As Task
Dim socket = New CoinbaseProWebSocket(New WebSocketConfig With {
.ApiKey = creds.ApiKey,
.Secret = creds.ApiSecret,
.Passphrase = creds.ApiPassphrase,
.SocketUri = "wss://ws-feed-public.sandbox.pro.coinbase.com"
})
WriteLine(">> Connecting websocket...")
Dim result = Await socket.ConnectAsync()
If Not result.Success Then Throw New Exception("Connect error.")
WriteLine(">> Connected.")
AddHandler socket.RawSocket.Closed, (AddressOf Websocket_Closed)
AddHandler socket.RawSocket.Error, (AddressOf Websocket_Error)
AddHandler socket.RawSocket.MessageReceived, (AddressOf Websocket_MessageReceived)
Dim Subsc = New Subscription
Subsc.ProductIds.AddRange({"BTC-EUR", "BTC-USD"})
Subsc.Channels.Add("ticker")
Subsc.Channels.Add("matches")
WriteLine(">> Subscribing to events...")
Await socket.SubscribeAsync(Subsc)
WriteLine(">> Subscribed.")
End Function
The event:
Private Shared Sub Websocket_MessageReceived(ByVal sender As Object, ByVal e As WebSocket4Net.MessageReceivedEventArgs)
WriteLine("Message received.")
Dim msg = Nothing, hb As HeartbeatEvent = Nothing, tk As TickerEvent = Nothing
Form1.BitcoinPriceLabel.Text = "Test to see if I can edit the label"
If WebSocketHelper.TryParse(e.Message, msg) Then
If CSharpImpl.__Assign(hb, TryCast(msg, HeartbeatEvent)) IsNot Nothing Then
' WriteLine($"Sequence: {hb.Sequence}, Last Trade Id: {hb.LastTradeId}")
End If
If CSharpImpl.__Assign(tk, TryCast(msg, TickerEvent)) IsNot Nothing Then
If tk.ProductId = "BTC-EUR" Then
WriteLine($"Coin: {tk.ProductId}, Last value: {tk.Price}, BestAsk: {tk.BestAsk}")
End If
End If
End If
End Sub
The issue is that you are using the default instance of Form1 on a secondary thread. Default instances are thread-specific so the instance you update is not the instance you have displayed. Read this for more information.
The solution is to invoke a method on the UI thread and then the default instance you use will be the same one you displayed. If you are in a class that is not a form or other control, the way to do that is with the SynchronizationContext class, e.g.
Private context As SynchronizationContext = SynchronizationContext.Current
'This method is executed on a secondary thread.
Private Sub BackgroundMethod()
context.Post(AddressOf ForegroundMethod, Nothing)
End Sub
'This method is executed on the UI thread.
Private Sub ForegroundMethod(state As Object)
'Update the UI here.
End Sub
You need to create the instance of that type on the UI thread and then the current context it gets will be that for the UI thread. That context is used to post a delegate to that thread later. The method you create the delegate for must have a single parameter of type Object. When you call Post pass any data you require to that parameter and then cast as the appropriate type in the posted method. In your case, the method might look like this:
Private Sub SetForm1BitcoinPriceLabelText(text As Object)
Form1.BitcoinPriceLabel.Text = text
End Sub
and you might call it like this:
context.Post(AddressOf SetForm1BitcoinPriceLabelText, "Test to see if I can edit the label")
For years I create delays in my software using, for example:
Wait(10000)
Sub Wait(milliseconds)
<here I get the current time>
Do
<here I loop until the current time passed in seconds and then exit this loop>
Application.DoEvents()
Loop
End Sub
The problem is, this uses a lot of CPU. I tried Thread.Sleep(1000), but this FREEZES my application while it's performing!
I tried using a Timer, but I STILL need a loop that doesn't freeze yet acts like Application.DoEvents(). It seems impossible.
My goal is to do this:
label1.text = "ok about to start"
Wait(5000)
' the following line CAN NOT run until after 5 seconds.
label1.text = "DONE"
How to execute code after a delay.
There are different methods to execute code after a delay or execute it asynchronously, after a delay or not. Here, I'm considering a Timer and simple implementations of the Async/Await pattern.
A loop that calls Application.DoEvent() should be avoided in any case.
► Using a Timer to delay the execution of a single instruction or the code in one or more methods:
You cannot await for a Timer, but you can use a method that creates a Timer and executes an Action when the Timer raises its event, signaling that the Interval specified has elapsed.
The method that follows accept as arguments a delay value and an Action delegate.
The Delay is used to set the Timers' Interval, the Action represent the code that will be executed when the Timer Ticks (I'm using a System.Windows.Forms.Timer here, since you seem to refer to a WinForms application).
Private waitTimer As New System.Windows.Forms.Timer()
Private TimerTickHandler As EventHandler
Private Sub Wait(delay As Integer, action As Action)
waitTimer.Interval = delay
TimerTickHandler = New EventHandler(
Sub()
action.Invoke()
waitTimer.Stop()
RemoveHandler waitTimer.Tick, TimerTickHandler
End Sub)
AddHandler waitTimer.Tick, TimerTickHandler
waitTimer.Start()
End Sub
We can call this method when we need to execute code after a delay.
The Action can be a simple instruction: in this case, the Text of label1 will be set to "Done" after 5 seconds, while the UI Thread continues its operations:
label1.text = "About to Start..."
Wait(5000, Sub() Me.label1.Text = "Done")
The Action can also be a method:
Private Sub SetControlText(control As Control, controlText As String)
control.Text = controlText
End Sub
' Elsewhere
Wait(5000, Sub() SetControlText(Me.label1, "Done"))
Of course the SetControlText() method can execute more complex code and, optionally, set a Timer itself, calling Wait().
► Using the Async/Await pattern.
An async method provides a convenient way to do potentially
long-running work without blocking the caller's thread. The caller of
an async method can resume its work without waiting for the async
method to finish.
In simple terms, adding the Async modifier to a method, allows to use the Await operator to wait for an asynchronous procedure to terminate before the code that follows is executed, while the current Thread is free to continue its processing.
▬ Note that the Async modifier is always applied to a Function() that returns a Task or a Task(Of something) (something can be any value/reference a method can return).
It's only applied to a Sub() when the Sub (void) method represents an Event Handler.
This is very important to remember and apply without exceptions (unless you're quite aware of the implications). ▬
Read the Docs about this (in the previous link) and these:
Async and Await
Don't Block on Async Code
A simple Async method can be used to delay the execution of an action:
Private Async Function Wait(delay As Integer, action As Action) As Task
Await Task.Delay(delay)
action?.Invoke()
End Function
This is similar to the Timer functions and acts in a similar way. The difference is that you can Await this method to both execute the code it runs asynchronously and to wait for its completion to run other code after the method returns.
The calling Thread (the UI Thread, here), will continue its operations while the Wait() method is awaited.
Assume that, in a Button.Click handler, we want to execute an Action (a method that doesn't return a value) or a Function (a method that returns a value) and the code that follows should execute only after this Action or Function returns:
Private Async Sub button1_Click(sender As Object, e As EventArgs) Handles button1.Click
Await Wait(5000, New Action(Sub() Me.label1.Text = "Done"))
Await Wait(5000, Nothing)
' (...)
' ... More code here. It will execute after the last Await completes
End Sub
Here, we instruct to wait for 5 seconds, then set the Text of a Label to a value, wait other 5 seconds, doing nothing, then execute the code that follows
If we don't need to perform an Action, we can simply use Task.Delay():
Private Async Sub button1_Click(sender As Object, e As EventArgs) Handles button1.Click
label1.text = "About to Start..."
Await Task.Delay(5000)
label1.Text = "Done"
' (...)
' ... More code here. It will execute after the last Await completes
End Sub
We can also use Async/Await to wait for the completion of a Task run in a ThreadPool Thread, calling Task.Run() to execute code in a Lambda expression:
(just an example, we shouldn't use a ThreadPool Thread for such a simple task)
Private Async Sub button1_Click(sender As Object, e As EventArgs) Handles button1.Click
label1.text = "About to Start..."
Await Task.Run(Async Function()
Await Task.Delay(5000)
BeginInvoke(New Action(Sub() Me.label1.Text = "Done"))
End Function)
' (...)
' ... More code here. It will execute after the last Await completes
End Sub
See also the Task.ContinueWith() method:
Creates a continuation that executes asynchronously when the target
Task completes.
I am currently working on a VB.NET form that automatically create Word documents according to an Excel file and a few extra data asked by the form (Project Name, Customer Name, Use SQL, ...).
This procedure works fine and takes approximatelly 1 or 2 minutes to complete.
The issue is that all my script is in ButtonGenerate.Click Handler. So when the Generate button is pressed the form window is bricked and it's impossible to Cancel...
It shouldn't be in a Click handler. Opening a new thread for that long task seems better. But Multithreading isn't very familiar to me.
I tryed launching the script with
ThreadPool.QueueUserWorkItem(...
but my Generate Sub sets labels and update a Progress Bar in the main form, so I doesn't work unless I use
Me.Invoke(New MethodInvoker(Sub()
label.Text = "..."
ProgressBar.Value = 10
' ...
End Sub)
each time I need to update something on the form and I can't even retrieve any new push of a button with that (A cancel button would be nice).
This is basically my code :
Public Class TestFichesAutomation
Private Sub BtnGenerate_Click(sender As Object, e As EventArgs) Handles BtnGenerate.Click
System.Threading.ThreadPool.QueueUserWorkItem(Sub() Generate())
End Sub
Public Sub Generate()
' Check user options, retrieve Excel Data, SQL, Fill in custom classes, create Word docs (~ 1 minute)
End Sub
So How would you handle that script ? Is Threading even a good solution ?
Thanks a lot for your help ^^ and for the useful doc.
My app now open a new thread and uses 2 custom classes to act like buffers :
Private Async Sub Btn_Click(sender As Object, e As EventArgs) Handles Btn.Click
myProgress = New Progress
' a custom class just for the UI with the current task, current SQL connection status and progress value in %
_Options.ProjectName = TextBoxProjectName.Text
_Options.CustomerName = TextBoxCustomerName.Text
...
' Fill in a custom "_Options" private class to act as a buffer between the 2 thread (the user choices)
Loading = New Loading()
Me.Visible = False
Loading.Show() ' Show the Loading window (a ProgressBar and a label : inputLine)
Task.Run(Function() Generate(Progress, _Options))
Me.Visible = True
End Sub
Public Async Function Generate(ByVal myProgress As Progress, ByVal Options As Options) As Task(Of Boolean)
' DO THE LONG JOB and sometimes update the UI :
myProgress.LoadingValue = 50 ' %
myProgress.CurrentTask= "SQL query : " & ...
Me.Invoke(New MethodInvoker(Sub() UpdateLoading()))
' Check if the task has been cancelled ("Cancelled" is changed by a passvalue from the Loading window):
If myProgress.Cancelled = True Then ...
' Continue ...
End Function
Public Shared Sub UpdateLoading()
MyForm.Loading.ProgressBar.Value = myProgress.LoadingValue
MyForm.Loading.inputLine.Text = myProgress.CurrentTask
' ...
End Sub
You should look into using the Async/Await structure
if the work you need to do is CPU bound, i like using Task.Run() doc here
By making your event handler Async and having it Await the work, you prevent the UI from locking up and avoid the use of Invoke in most cases.
ex:
Private Async Sub Btn_Click(sender As Object, e As EventArgs) Handles Btn.Click
Dim Result As Object = Await Task.Run(Function() SomeFunction())
'after the task returned by Task.Run is completed, the sub will continue, thus allowing you to update the UI etc..
End Sub
For progress reporting with Async/Await you might be interested in this
Is it possible to invoke a function from a "non main" thread and then wait for it to finish before executing the rest?
I could set a boolean just before and then make the function "flip" the boolean to false when its done, but I wondered if there was a simplier/more professional way of achieving this?
Thanks
I guess you want to keep your form responsive yet you don't want to have extra procedures being called or stuff like that.
In that case the Async and Await keywords are probably a good way for you:
It is explained in detail here http://msdn.microsoft.com/en-US/en-en/library/hh191443.aspx but I will give you a short overview:
You first declare a method with the Async keyword. This can be for example the method handling a button click event in my example below.
In this method you get your result from another method that is called as a task and assign this to a temporary variable using the await keyword.
During the time the task is running the further execution of the code is halted. Your GUI stays responsive tough.
Here is a small example (just throw a Button and a Label on a Form):
Public Class Form1
''' <summary>
''' This method does the work. It is called from the async method in form of a Task(Of String).
''' </summary>
Private Function GetString() As String
'Some delay
Threading.Thread.Sleep(3000)
Return "Hello World!"
End Function
'Note the Async Keyword
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'First create the task
Dim t As Task(Of String) = New Task(Of String)(AddressOf GetString)
'Start the task
t.Start()
'Wait for the task to complete. Does not suspend your GUI!
'Much preferrable to some kind of waiting loop with DoEvents and stuff.
Dim Result As String = Await t
'Signal the end
MsgBox("DONE")
'Output the results
Label1.Text = Result
End Sub
End Class
I'll be honest, I can't really dive into the dark underbellies of how this is actually implemented in the .NET Framework since I have not read much about it myself. (I program mainly for the .NET Framework 4.0. Async/Await was introduced in 4.5. It can however be used in 4.0 as well with an extension package by Microsoft https://www.nuget.org/packages/Microsoft.Bcl.Async).
The actual usage however is not that hard as you can see, so I think this is the way to go.
Well, I guess that I'm not the first who ask this, but what is the easiest way to use threads in VB.NET? I mean, I need to download some string from a remote server and then to show that string in the GUI, so I have to use some callback function to call it in the main thread. I found different approaches for this, but all seems very difficult compared to Python where (with GTK) I used something like:
gobject.idle_add(callback_function, parameters)
and "callback_function" was executed in the main thread. How I do that in VB.NET?
Here's a simple example. It may be a little more difficult than some other languages, but it's still not terribly complicated. The following code assumes that it is within a form class (if not, you'd need to use some control or form reference to call Invoke):
Private Sub beginDoWork()
Dim thread As New Thread(AddressOf DoWork)
thread.Start()
End Sub
Public Sub DoWork()
Dim result As String = getStringFromRemoteServer()
workCompleted(result)
End Sub
Private Delegate Sub workCompletedDelegate(result As String)
Private Sub workCompleted(result As String)
If InvokeRequired Then
Invoke(New workCompletedDelegate(AddressOf workCompleted(result)
Exit Sub
End If
Label1.Text = result
End Sub
This could be further simplified by just having DoWork always call Invoke to call workCompleted rather than have workCompleted check if the invoke is required, but the way I wrote it is a bit more encapsulated and efficient if you are ever going to do the work on the UI thread instead of a worker thread.