BackgroundWorkers - ProgressChanged for static progress - vb.net

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

Related

Updating DataGridView.BackColor on a background thread

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.

How to do something on main thread?

I used to be an xcode programmer.
There, when creating application, I tend to do most things on other threads.
Occasionally, such as when I want to access the UI thread, I would then do something at main thread.
Say I did
//Load some heavy resources on the web
doOnMainThread(sub () updateUIandStuff())
//Continue doing other things.
How would I implement doOnMainthread in VB?
There is an easy way to do so in objective-c a long time ago. How to do so in vb.net
There are two common methods to accomplish this; Delegates or Invoke a lambda. The following example will paste straight into a new WinForms project with two labels added in the designer:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim bw As New System.ComponentModel.BackgroundWorker
AddHandler bw.DoWork, AddressOf BackgroundWorker_DoWork
bw.RunWorkerAsync()
End Sub
Delegate Sub UpdateLabelDelegate(ByVal labelText As String)
Private Sub UpdateLabel(ByVal labelText As String)
Label2.Text = labelText
End Sub
Private Sub BackgroundWorker_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs)
For i As Integer = 1 To 10000
Dim testText As String = "Loop#" & i.ToString
'You can easily check your thread's access to the UI using:
' WinForms "If InvokeRequired Then : End If"
' WPF "If Not Dispatcher.CheckAccess Then : End If"
'Using Invoke Lambda
Invoke(Sub() Label1.Text = testText)
'Using Delegate
Dim updateLbl As UpdateLabelDelegate = AddressOf UpdateLabel
Invoke(updateLbl, testText)
Next
End Sub
End Class
The first example Invoke(Sub() Label1.Text = testText) is my preferred method for when the amount of code that needs to be executed on the main thread is small, like a line or two. If that line is placed in a sub that may be called by the main UI thread or a background thread in different scenarios, then it should be wrapped in a If InvokeRequired Then conditional block. The second example is using a delegate, and this method is better if a larger number of lines of code need to be executed on the main thread. Either method will allow you to call code from a background thread that will be executed on the main UI thread.
As for WPF, the methodology is largely the same, but as Bradley Uffner pointed out in the comments, invoke will be called by Dispatcher

WinForms.IllegalCrossThreadCall with filewatcher

I'm new to Visual Basic and overall kind of new to coding in general.
Currently I work on a program which uses a filewatcher. But If I try this:
Public Class Form1
Private WithEvents fsw As IO.FileSystemWatcher
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
fsw = New IO.FileSystemWatcher("PATH")
fsw.EnableRaisingEvents = True
' fsw.Filter = "*.settings"
End Sub
Private Sub GetSettingsFromFile()
Some Code
More Code
CheckBox1.Checked = True
End Sub
Private Sub fsw_Changed(sender As Object, e As FileSystemEventArgs) Handles fsw.Changed
fsw.EnableRaisingEvents = False 'this is set because the file is changed many times in rapid succesion so I need to stop the Filewatcher from going of 200x (anyone has a better idea to do this?)
Threading.Thread.Sleep(100)
GetSettingsFromFile()
fsw.EnableRaisingEvents = True 'enabling it again
End Sub
End Class
But when I do this (trying to change anyhting in the form) I get this error:
System.InvalidOperationException (WinForms.IllegalCrossThreadCall)
It wont stop the program from working, but I want to understand what is wrong here and why the debugger is throwing this at me
regards
The event is being raised on a secondary thread. Any changes to the UI must be made on the UI thread. You need to marshal a method call to the UI thread and update the UI there. Lots of information around on how to do that. Here's an example:
Private Sub UpdateCheckBox1(checked As Boolean)
If CheckBox1.InvokeRequired Then
'We are on a secondary thread so marshal a method call to the UI thread.
CheckBox1.Invoke(New Action(Of Boolean)(AddressOf UpdateCheckBox1), checked)
Else
'We are on the UI thread so update the control.
CheckBox1.Checked = checked
End If
End Sub
Now you simply call that method wherever you are and whatever thread you're on. If you're already on the UI thread then the control will just be updated. If you're on a secondary thread then the method will invoke itself a second time, this time on the UI thread, and the control will be updated in that second invocation.

Console APP with Timer not running

I did this first into a WinForm project, now I've changed the application type to "Console application", I've deleted the form1.vb, changed the startup object to this "Module1.vb" but now I can't run the app.
well the app runs but the timer tick is doing nothing, the code is exactly the same, I only did one change for the sub main/form1_load name
What I'm doing wrong?
PS: I've tested if the error was in the conditional of the lock method and all is good there, the problem is with the ticker event but I don't know why.
#Region " Vars "
Dim Running As Boolean = False
Dim Errors As Boolean = False
Dim Executable_Name As String = Nothing
Dim Toogle_Key As System.Windows.Forms.Keys = Nothing
Dim WithEvents Toogle_Key_Global As Shortcut = Nothing
Dim Executable_Timer As New Timer
Dim Lock_Timer As New Timer
Dim Lock_Interval As Int32 = 10
Dim Lock_Sleep As Int32 = Get_Milliseconds(3)
Dim Screen_Center_X As Int16 = (Screen.PrimaryScreen.Bounds.Width / 2)
Dim Screen_Center_Y As Int16 = (Screen.PrimaryScreen.Bounds.Height / 2)
#End Region
' Load
Sub main()
Pass_Args()
Sleep()
Lock()
End Sub
' Lock
Private Sub Lock()
If Process_Is_Running(Executable_Name) Then
AddHandler Lock_Timer.Tick, AddressOf Lock_Tick
AddHandler Executable_Timer.Tick, AddressOf Executable_Tick
Lock_Timer.Interval = Lock_Interval
Lock_Timer.Start()
Executable_Timer.Start()
Running = True
Else
Terminate()
End If
End Sub
' Lock Tick
Private Sub Lock_Tick()
Console.WriteLine("test")
If Running Then Cursor.Position = New Point(Screen_Center_X, Screen_Center_Y)
End Sub
UPDATE
I made these changes like in the examples of MSDN:
Dim Executable_Timer As New System.Timers.Timer
Dim Lock_Timer As New System.Timers.Timer
AddHandler Lock_Timer.Elapsed, AddressOf Lock_Tick
AddHandler Executable_Timer.Elapsed, AddressOf Executable_Tick
But the tick/elapsed is still doing nothing...
FROM MSDN
Windows.Forms.Timer
Implements a timer that raises an event at user-defined intervals.
This timer is optimized for use in Windows Forms applications and must
be used in a window.
You need a System.Timer
Of course this requires a different event Handling
(Example taken from MSDN)
' Create a timer with a ten second interval.
Dim aTimer = new System.Timers.Timer(10000)
' Hook up the Elapsed event for the timer.
AddHandler aTimer.Elapsed, AddressOf OnTimedEvent
....
Private Shared Sub OnTimedEvent(source As Object, e As ElapsedEventArgs)
Console.WriteLine("The Elapsed event was raised at {0}", e.SignalTime)
End Sub
You could use Windows.Forms.Timer in console application if you add Application.Run() at the end of your main().
This kind of timer might be useful in some console applications if you are using any offscreen Windows.Forms object - ie.: for offscreen rendering - these objects can't be simply accessed from System.Timer since it fires on separate thread (than the one where Windows.Forms object was created on).
Otherwise by all means use the System.Timers.Timer or System.Threading.Timer

How can the BackgroundWorker get values from the UI thread while it is running?

I am using the BackgroundWorker to do the heavy tasks so the UI thread doesn't get blocked. While the BackgroundWorker can send values to the UI thread using the progress-scheme, how can the BackgroundWorker get some values FROM the UI thread?
Either by asking it or simply by the UI thread sending some values to the BackgroundWorker?
Just accessing a variable of the UI thread like UIForm.x within the BackgroundWorker does not work, it does not seem to have access to the UI variables???
Many thanks
Other threads than the UI thread are not allowed to access the UI. You probably started the BackgroundWorker with worker.RunWorkerAsync(). You can also start it with worker.RunWorkerAsync(someObject). While the worker is running, you cannot pass new objects, but you can alter the contents of the object itself. Since object types are reference types, the UI thread and the worker thread will see the same object content.
Imports System.ComponentModel
Imports System.Threading
Class BgWorkerCommunication
Private _worker As BackgroundWorker
Private Class WorkParameters
Public text As String
End Class
Public Sub DoRun()
Dim param = New WorkParameters()
_worker = New BackgroundWorker()
AddHandler _worker.DoWork, New DoWorkEventHandler(AddressOf _worker_DoWork)
AddHandler _worker.RunWorkerCompleted, New RunWorkerCompletedEventHandler(AddressOf _worker_RunWorkerCompleted)
param.text = "running "
_worker.RunWorkerAsync(param)
While _worker.IsBusy
Thread.Sleep(2100)
param.text += "."
End While
End Sub
Private Sub _worker_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
Console.WriteLine("Completed")
End Sub
Private Sub _worker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
Dim param = DirectCast(e.Argument, WorkParameters)
For i As Integer = 0 To 9
Console.WriteLine(param.text)
Thread.Sleep(1000)
Next
End Sub
End Class