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")
Related
I have an application with a DataGridView on which multiple people could be working at the same time. I want to have each user's current row location displayed via a different colour row in the DataGridView.
Previously I was doing all of this updating via the RowEnter event however the performance is not satisfactory, for obvious reasons.
I'm trying to have a background thread which loops every 10 seconds to populate a DataTable with keys of the other users' locations which then references a key column in the DGV, and if they match, change the DGV row background color else set it to the default.
My current code, below, loops every 10s but it doesn't actually update the DGV.
Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ActiveThread = True
dgvThread = New Thread(AddressOf UpdateDGVFromThread) With {
.IsBackground = True}
dgvThread.Start()
End Sub
Public Sub UpdateDGVFromThread()
Do While ActiveThread = True
'Sets table with key values
dtUsers = CLS_USERS.GetUsers(User)
'Loop through them
For Each row As DataRow In dtUsers.Rows
intSeq = row("SEQUENCE")
'Loop through each DGV row and compare the values
For Each dgv_row As DataGridViewRow In dgvCandList.Rows
dgvCandList.BeginInvoke(
Sub()
If dgv_row.Cells("CURRENT_CAND_SQ").Value = intSeq Then
dgv_row.DefaultCellStyle.BackColor = Color.DarkCyan
Else
dgv_row.DefaultCellStyle.BackColor = Color.Cyan
End If
End Sub)
Next
Next
Thread.Sleep(10000)
Loop
End Sub
I tried using dgv.Invoke() rather than .BeginInvoke() but this seemed to lock up the UI thread constantly and only the DGV was unlocked.
Can anyone point me in the right direction?
The BeginInvoke method is used to asynchronously invoke a method delegate on the thread that created the Control's handle. The UI thread, here. It's signature is:
Public Function BeginInvoke (method As Delegate) As IAsyncResult
The method Delegate is then declared in the same thread where the Control invoked has been created.
The delegate should then be declared like this:
In the UI thread:
Delegate Sub MyUpdateDelegate()
Public Sub MyUpdateMethod()
[SomeControl].Text = "Updated Text"
End Sub
In another thread:
Private Sub InvokeFromAnotherThread()
'Prefer the Parent Form as marshaller
Me.BeginInvoke(New MyUpdateDelegate(AddressOf MyUpdateMethod))
'(...)
'You can also use a Control, but the Parent Form is better
[SomeControl].BeginInvoke(New MyUpdateDelegate(AddressOf MyUpdateMethod))
End Sub
Using an anonymous method in-place won't cut it.
There's a shortcut, provided by the MethodInvoker delegate:
MethodInvoker provides a simple delegate that is used to invoke a
method with a void parameter list. This delegate can be used when
making calls to a control's Invoke method, or when you need a simple
delegate but do not want to define one yourself.
Using a MethodInvoker delegate, there's no need to declare a delegate in the UI thread. An anonymous method can be used here, it will be invoked in the UI thread:
Private Sub InvokeFromAnotherThread()
'(...)
BeginInvoke(New MethodInvoker(Sub() [SomeControl].Text = "Updated Text"))
'(...)
End Sub
Or:
Private Sub InvokeFromAnotherThread()
'(...)
BeginInvoke(New MethodInvoker(
Sub()
[SomeControl].Text = "Updated Text"
[SomeOtherControl].BackColor = Color.Red
End Sub))
'(...)
End Sub
Why I suggested a Timer:
The thread you're using has one task only: update a Control in the UI thread and then sleep.
To perform this task, it needs to invoke a method in the UI thread. If the reason why the thread has been created is to avoid blocking the UI thread, a Timer will do the same thing. A System.Windows.Forms.Timer, specifically, will raise its Tick event in the UI thread, without cross-thread calls.
The practical effect is more or less the same.
I’ve got a problem with form freezes when loading the form from an event. I bet it got to do with threading but sadly I don’t know enough about it to fix it myself :(
Let me explain my project:
I've got a class that hooks into networking events (like new connected e.g.),
which I’ve instanced in a form and declared some events from it.
Public Netstat As New aaNetTool.clsNetworkStatus
AddHandler Netstat.NetworkChanged, AddressOf Network_Changed
Sub Network_Changed()
End Sub
Then I’ve written another class, clsMessage which I want to use to show forms with notifications.
Public Class clsMessage
Private myForm As frmDisplayMessage
Public Sub New(ByVal Title$, ByVal Text$, Optional btnYesAction As Action = Nothing, Optional ByVal ShowTimeSec% = 10)
myForm = New frmDisplayMessage
myForm.Text = Title
myForm.lblText.Text = Text
(...)
myForm.Show()
(...)
End Sub
Now I create a new notification window for debugging purposes with a button from the main form like this:
Dim myMsg As New clsMessage("title", "text", AddressOf MapNetworkdrives, 30)
This works like a charm.
But when I call the notification from my declared event:
Sub Network_Changed()
Dim myMsg As New clsMessage("title", "text", AddressOf MapNetworkdrives, 30)
End Sub
The form with the notification appears but is empty and freezed.
As said before I think this may have to do with my code running on different threads but I just can't figure out how to solve this :(
Thanks in advance for your Time,
Lunex
The clsNetworkStatus.NetworkChanged event appears to be raised from a background thread. Since your notification form is part of the UI you must invoke so that it is executed under the UI thread.
The InvokeRequired property tells you whether you need to invoke or not, so if it's False your code is already running on the UI thread.
You can create an extension method to do the checking for you:
Imports System.Runtime.CompilerServices
Public Module Extensions
<Extension()> _
Public Sub InvokeIfRequired(ByVal Control As Control, ByVal Method As Action)
If Control.InvokeRequired = True Then
Control.Invoke(Method) 'Invoke the method thread-safely.
Else
Method.Invoke() 'Call the method normally (equal to just calling: 'Method()').
End If
End Sub
End Module
Then you'd use it like this:
Sub Network_Changed()
Me.InvokeIfRequired(Sub()
Dim myMsg As New clsMessage("title", "text", AddressOf MapNetworkdrives, 30)
End Sub)
End Sub
I want to use a backgroundworker to poll a hardware sensor very frequently without leaving my UI inoperable.
Because the backgroundworker simply polls until interrupted - runtime is purely dictated by the user interrupting it - it has no change in progress so to speak.
If I call ReportProgress with a constant value, e.g. ReportProgress(1), will this still call ProgressChanged? I require ProgressChanged to update the UI in accordance with the latest poll data.
The value passed as first parameter to ReportProgress just serves at your code on the UI thread to display the advancement of your background task.
It has no importance for the execution of the call to ProgressChanged.
If you need to communicate some different data to your ProgressChanged event you could use the overload of ReportProgress that takes two arguments and allows to pass the instance of a custom object as second parameter.
In this very trivial example, I have defined a class named WorkingStatus with just one property that I change in the DoWork method, then I pass an instance of this class to the ProgressChanged event. Of course your WorkingStatus class could be more complex with all the informations that you want to display on the UI thread
public class WorkingStatus
public Current as Integer
'.... other properties as needed....
End Class
Sub Main
Dim bkw = new BackgroundWorker()
bkw.WorkerReportsProgress = true
AddHandler bkw.ProgressChanged, AddressOf bgw_ProgressChanged
AddHandler bkw.DoWork, AddressOf bgw_DoWork
bkw.RunWorkerAsync()
' This loop just to avoid the immediate close of the example
Dim counter = 0
While (bkw.IsBusy)
counter+=1
Console.WriteLine("IsBusy " & counter.ToString())
Thread.Sleep(150)
End While
End Sub
private sub bgw_DoWork(sender as object, e as DoWorkEventArgs)
Dim bgw = DirectCast(sender, BackgroundWorker)
Dim sts = new WorkingStatus() With {.Current = 0}
' A simulation of your inner working
for i = 0 to 10
Thread.Sleep(5000)
sts.Current+=1
bgw.ReportProgress(1, sts)
Next
Console.WriteLine("Background DoWork ENDED")
End Sub
private sub bgw_ProgressChanged(sender as object, e as ProgressChangedEventArgs)
Dim sts = DirectCast(e.UserState, WorkingStatus)
Console.WriteLine("Progress:" & e.ProgressPercentage.ToString() & ", Status=" & sts.Current)
End Sub
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)
For some reason a background thread in my app can't change any labels, textbox values, etc on my main form. There is no compile errors, when the thread executes nothing happens.
Here is some example code:
Imports System.Threading
Public Class Class1
Dim tmpThread As System.Threading.Thread
Private Sub bgFindThread()
Form1.lblStatus.Text = "test"
End Sub
Public Sub ThreadAction(ByVal Action As String)
If Action = "Start" Then
tmpThread = New System.Threading.Thread(New System.Threading.ThreadStart(AddressOf bgFindThread))
tmpThread.Start()
ElseIf Action = "Abort" Then
If tmpThread.IsAlive = True Then tmpThread.Abort()
End If
End Sub
End Class
Can someone let me know what I'm doing wrong?
AFAIK code above will throw an exception IllegalCrossThreadException, it is because the background thread is not the same as UI thread and background try to set value on other thread. So windows form check every thread that work properly.
You can set Control.CheckForIllegalCrossThreadCalls to false to make it works.
Code below is when setting property is not run
Add into your code
------------------------------
Delegate Sub MyDelegate()
Private Sub RunMyControl()
lblStatus.Text = "test"
End Sub
Change your code
------------------------------
Private Sub bgFindThread
lblStatus.BeginInvoke (New MyDelegate(AddressOf RunMyControl))
End Sub
The method asyncronsly run code from background thread to UI thread.
You can only access UI controls from the UI thread.
I suggest reading this first: http://www.albahari.com/threading/
As others have mentioned, it is forbidden (for good reasons) to update UI elements from a non-UI thread.
The canonical solution is as follows:
Test whether you are outside the UI thread
If so, request for an operation to be performed inside the UI thread
[Inside the UI thread] Update the control.
In your case:
Private Sub bgFindThread()
If lblStatus.InvokeRequired Then
lblStatus.Invoke(New Action(AddressOf bgFindThread))
Return
End If
lblStatus.Text = "test"
End Sub
The only thing that changed is the guard clause at the beginning of the method which test whether we’re inside the UI thread and, if not, requests an execution in the UI thread and returns.
You can use a delegate to update UI controls in a background thread.
Example
Private Delegate Sub bkgChangeControl(ByVal bSucceed As Boolean)
Private dlgChangeControl As bkgChangeControl = AddressOf ChangeControl
Private Sub threadWorker_ChangeControl(ByVal bSucceed As Boolean)
Me.Invoke(dlgChangeControl, New Object() {bSucceed})
End Sub
Private Sub ChangeControl()
Me.lable="Changed"
End Sub
'In your background thread, call threadWorker_ChangeControl.