How to handle errors in a cross thread operation - vb.net

I have a webbrowser control created at runtime and used via a background thread. The following is an example of the code used:
If Me.InvokeRequired Then Me.Invoke(Sub() webbroswers(3).Document.GetElementById("ID").InnerText = TextBox4.Text)
This works great! But sometime the webbrowser doesn't have the element "ID" (for example). So I'd like a method to basically allow the code to continue if an error occurs. I've tried the try - catch block but this doesn't catch it!

You can use a multi-line lambda expression, like this:
If Me.InvokeRequired Then Me.Invoke(
Sub()
Dim element As HtmlElement = webbroswers(3).Document.GetElementById("ID")
If element IsNot Nothing Then
element.InnerText = TextBox4.Text
End If
End Sub
)
Checking if it's Nothing, like that, is more efficient than letting it fail and catching the exception. However, if you need to do a Try/Catch for any other reason, you can also do that easily in a multi-line lambda expression, for instance:
If Me.InvokeRequired Then Me.Invoke(
Sub()
Try
webbroswers(3).Document.GetElementById("ID").InnerText = TextBox4.Text
Catch ex As Exception
' ...
End Try
End Sub
)
However, if the lambda expression gets too long, or if you'd like to have more meaningful stack traces in your exceptions, you can use a delegate to an actual method, like this:
If Me.InvokeRequired Then Me.Invoke(AddressOf UpdateId)
'...
Private Sub UpdateId()
Try
webbroswers(3).Document.GetElementById("ID").InnerText = TextBox4.Text
Catch ex As Exception
' ...
End Try
End Sub

Related

Timers and variables in vb.net

I have a class with a timer. The timer starts in the new method. The method associated with the timer is checkMatch() but checkTimer() ensures the parameters are met to start the timer. They are met and the timer starts. The method checkMatch() is currently rewritten to only execute once since I know it keeps repeating itself. The only place to change the boolean variable oneTime is located inside of checkMatch(). I included the autoLoad() function just in case it is needed. Whenever I launch this class, the forms associated with the other methods not listed will pop up and instantly go away. The console shows that checkMatch() is being called regularly (which it should), but that the variable oneTime remains True each time it is called. The timer is started at the end of the autoLoad() method. I know all the methods in autoLoad() are being called and executed.
Since oneTime is showing as True constantly in the console, I am assuming I have a conception issue somewhere. Why is oneTime true each time checkMatch() is called?
' a user is required for this class
Public Sub New(ByVal my_user As RGOUser)
My.Settings.selected_summoner = "huge legend"
My.Settings.Region = "na1"
My.Settings.Save()
myUser = my_user
AddHandler tmr.Elapsed, AddressOf checkMatch
checkTimer()
End Sub
Public Sub checkMatch()
Console.WriteLine(Me.GetType.ToString() + "||" + System.Reflection.MethodInfo.GetCurrentMethod.ToString())
Console.WriteLine(oneTime)
Try
Dim psList() As Process = Process.GetProcesses()
For Each p As Process In psList
' If strLeagueProcessName = p.ProcessName) And Not boolInGame Then
' remove later and replace with commented line
If oneTime Then
Console.WriteLine("checkMatch Success.")
boolInGame = True
oneTime = False
tmr.Interval = timerInsideMatch
tmr.Stop()
Try
autoLoad()
Catch ex As Exception
End Try
Return
Else
Return
End If
Next
Catch ex As Exception
Console.WriteLine("checkMatch Failure. " + ex.Message)
End Try
boolInGame = False
tmr.Interval = timerOutsideMatch
End Sub
Private Sub autoLoad()
Console.WriteLine(Me.GetType.ToString() + "||" + System.Reflection.MethodInfo.GetCurrentMethod.ToString())
If searchMatch() Then
Dim x As New System.Threading.Thread(AddressOf loadConfiguration)
Dim y As New System.Threading.Thread(AddressOf loadMatch)
Dim z As New System.Threading.Thread(AddressOf launchMinimap)
If My.Settings.configuration_auto_load Then
Try
x.Start()
Catch ex As Exception
End Try
End If
If My.Settings.loadMatch Then
Try
y.Start()
Catch ex As Exception
End Try
End If
If My.Settings.launchMinimap Then
Try
z.Start()
Catch ex As Exception
End Try
End If
x.Join()
y.Join()
z.Join()
tmr.Start()
End If
End Sub

Throwing exception from sub to async call

This is a windows forms application in which I have a particular form. On this form I display the progress of some processing that is supposed to happen in the background asynchronously. All of it works great, except for when I try to handle exceptions that are caught within the background processing....
This is the sub in my form's code that calls the Async function, which is in a module containing all the background processing code:
Public Async Sub BasicProcessing()
Try
Dim processTarget As Action(Of Integer)
processTarget = AddressOf UpdatePulseProcessing
myProgress = New Progress(Of Integer)(processTarget)
myCount.Vehicles = Await ProcessmyCountFile(myCount, myProgress)
If OperationCanceledByUser = True Then
Exit Sub
End If
Catch ex As Exception
MessageBox.Show(Me, "Unable to update count." _
& Environment.NewLine & ex.Message, _
"Error updating count", MessageBoxButtons.OK, MessageBoxIcon.Error)
Exit Sub
End Try
End Sub
This is the async function that it calls, which is in a separate module:
Public Function ProcessmyCountFile(CountToProcess As Count, ByVal ProgressObject As IProgress(Of Integer)) As Task(Of List(Of Vehicle))
myProgressObject = ProgressObject
basicToken = New CancellationTokenSource
Try
Return CType(Task(Of List(Of Vehicle)).Run(Function()
If basicToken.IsCancellationRequested Then
Return Nothing
Exit Function
End If
myCountFile = CountToProcess
MyVehicles = New List(Of Vehicle)
'All that is important in here to note is a call to a regular sub within this module
CreateVehicles()
Return MyVehicles
End Function, basicToken.Token), Global.System.Threading.Tasks.Task(Of List(Of Global.STARneXt.Vehicle)))
Catch ex As Exception
Throw New Exception(ex.Message)
Return Nothing
End Try
End Function
Public Sub StopProcess()
If Not basicToken Is Nothing Then
basicToken.Cancel() ' We tell our token to cancel
End If
End Sub
This is the regular sub called by the Async function:
Private Sub CreateVehicles()
Try
'In here are calls to other regular subs within the same module, let's just call them A and B
Catch ex As Exception
StopProcess()
Throw New Exception("Error creating vehicles at pulse " & pulsePointer & ". " & ex.Message)
End Try
End Sub
When I run this code with data that I know ends up generating an error in sub B, the error does propagate up, as far as up to the method that is directly called by the async function....
So when running in VS, it will stop at "Throw New Exception("Error creating vehicles at pulse " & pulsePointer & ". " & ex.Message)", with the Message containing the message thrown by sub B.
This is what the debugger says on that line:
An exception of type 'System.Exception' occurred in MyProject.exe but
was not handled in user code. Additional information: Error creating
vehicles at pulse....[error message propagated up as thrown by called
subs]. Arithmetic operation resulted in an overflow.
Then strangely enough, within the debugger if I hit "Step Into", it does then return to the sub in my form that called the Async function, which shows the message box on the GUI.
So how do I get this to automatically return back up to the original form code to show the message box? Why does it stop where it stops without continuing to propagate?
FYI, I did find this question, but it ultimately did not help.
#StephenCleary was right - I created a new install for my project as it is, and in the installed version I do get the message box with the expected error message.
Strange behavior on the part of the debugger, and a bit discouraging, but none-the-less I am glad that my code as laid out in my question does actually work.

Exception handling in TPL

I am using the following code for creating some files.
I have observed that sometimes if some exception occurs all Parallel.For threads stop in between.
I have few questions.
Should I be using AggregateException in CreateReport method or its # rt place.
How to make sure that if exceptions arises in any of the threads it does not stop other parallel threads.
Try
dtScheduledReports = objReprotHelper.GetTopImmediateReportsForExecution()
Parallel.For(0, dtScheduledReports.Rows.Count, Sub(i)
CreateReport(dtScheduledReports.Rows(i))
End Sub)
Catch ae As AggregateException
For Each ex As Exception In ae.InnerExceptions
ExceptionHandler.LogError(ex)
Next
End Try
Private Sub CreateReport(dtRow As DataRow, scheduleType As Integer)
Try
//do something
Catch
throw
End Try
End Sub
You can use a ConcurrentQueue(Of Exception) to enable safe exception enqueueing from multiple threads. This allows to execute the entire loop and throws all exceptions in an AggregateException.
Private Sub DoAParalelJobAndThrowErrors
Dim exQueue as New ConcurrentQueue(Of Exception)
dtScheduledReports = objReprotHelper.GetTopImmediateReportsForExecution()
// Execute the complete loop and capture all exceptions.
Parallel.For(0, dtScheduledReports.Rows.Count, Sub(i)
Try
CreateReport(dtScheduledReports.Rows(i))
Catch ex as Exception
exQueue.Enqueue(ex)
End Try
End Sub)
If exQueue.count > 0 throw new AggregateException(exQueue)
End Sub
Private Sub CreateReport(dtRow As DataRow, scheduleType As Integer)
//do something
End Sub
According MSDN this way doen't break the loop, all rows should be processed.

how to do rename the label name from 1 to 75 at run time in vb.net?

Public Sub Rename(ByVal labelno As Integer)
Try
Dim lbl As New Label
lbl = Controls("Label" & labelno.ToString())
lbl.Text = ReceivedFrame.ToString()
Catch ex As Exception
MessageBox.Show(ex.Message.ToString())
End Try
i want to change Label Text from 1 to 50. Label no given as input. i tried the above code. its working in other project. but in my current project it is not working Error is showing like this (cross Thread operation not valid.control label1 accessed from a thread.other than the thread it was created on)and i want to change the label text only.label&Label no is working properly i verified. is there any other way to rename label in vb.net?
You are trying to modify a property of a control which resides on a different thread than the thread from which you call Rename(). When updating controls you should check if it is required to send the call to the appropriate thread, or "invoke" the call. This should work.
Method 1: Always Invoke
Public Sub Rename(ByVal labelno As Integer)
Try
Dim lbl As New Label
lbl = Controls("Label" & labelno.ToString())
lbl.Invoke(Sub() lbl.Text = ReceivedFrame.ToString())
Catch ex As Exception
MessageBox.Show(ex.Message.ToString())
End Try
End Sub
It will always invoke the call, which is unnecessarily costly when Rename() is called from the control's thread. But it should always work. You can also do it this way to only invoke when required as not to make unnecessary invocations to the UI thread.
Method 2: Invoke only if required
Public Sub Rename(ByVal labelno As Integer)
Try
Dim lbl As New Label
lbl = Controls("Label" & labelno.ToString())
changeLabelText(lbl, ReceivedFrame.ToString())
Catch ex As Exception
MessageBox.Show(ex.Message.ToString())
End Try
End Sub
Private Delegate Sub changeLabelDel(lbl As Label, txt As String)
Private Sub changeLabelText(lbl As Label, txt As String)
If lbl.InvokeRequired Then
lbl.Invoke(New changeLabelDel(AddressOf ChangeLabelText), lbl, txt)
Else
lbl.Text = txt
End If
End Sub
Oh, one more thing. You can automate the whole invoke required check for future use. Put the extension method in a module
Method 3: Automate the invoke required pattern, reusable code
<Extension() _
Public Sub InvokeIfRequired(ByVal c As Control, mi As MethodInvoker)
If c.InvokeRequired Then
c.Invoke(mi)
Else
mi()
End If
End Sub
And call it like this:
Public Sub Rename(ByVal labelno As Integer)
Try
Dim lbl As New Label
lbl = Controls("Label" & labelno.ToString())
lbl.InvokeIfRequired(Sub() lbl.Text = ReceivedFrame.ToString())
Catch ex As Exception
MessageBox.Show(ex.Message.ToString())
End Try
End Sub
There's a wealth of information on this site regarding this topic. See
What does 'InvokeRequired' and 'Invoke' mean in .Net?
VB.net, Invoke, delegates, and threading. Can't figure out how to use them across classes

Is it correct to raise custom events from Timer/ BackgroundWorker Completed that go on and update UI

My winforms application needs to interact with hardware devices.
The display is kind of workflow... after step 1 .. completed.. go to step 2 .. etc.
The display is dynamic with controls being made visible at run time and activity being initiated (each of these User controls use timer/ BGWorker within).
I m Raising custom events from timer/ BGWorker_Completed. this will help proceed to the next step and update UI. Am I doing the right thing?
Im a newbie in winforms and Im just not able to figure out why the display is failing.
Im not catching any exception... but after a particular step I do not see the controls !!! I do not know where and how to debug this scenario. If I execute the main form as a stand alone.. I can see the display as well.
however if I navigate from the login page/ change the tabs within the main form and return back.. I do not see the display.
I tried putting in checks before calling the update on UI and in the same order as below.
Thread.Current.IsBackground returns false, control.IsHandleCreated returns true or else Im creating it with dim x=Control.handle()) Me.InvokeRequired/ Control.invokeRequired returns false (the way I wanted). However I do not see userControl being displayed... visibility/color/ everything is being set (in the program).. and Im able to see the hardware interaction!!!.. But no display :-( (step 4 and later)
Im not doing anything fancy in the login page...or in tabChanged events.
(On tab changed event... Im only cleaning up open connections/ closing bg workers if any.. which would be connected back whenever required)
Please let me know if I need to do anything...and how to solve this problem.
I m also calling EnsureHandleIsCreated(control) subroutine soon after initialize component of every user control/ main form.
'Code in Login Form
Dim myForm as new MainForm()
myForm.ShowDialog(Me) ' here i also tried with show/showDialog.. with/without ownerForm
Me.Hide() ' Hide login page
'Code for checking if handle is created or not
Public Sub CheckForInvalidThread()
frmMain.CheckForIllegalCrossThreadCalls() = True
If Thread.CurrentThread.IsBackground Or Not Thread.CurrentThread.Name Is THREAD_MAIN_NAME Then
Throw New InvalidOperationException(THREAD_IS_INVALID)
End If
If Not Me.IsHandleCreated Then
Dim x = Me.Handle()
Thread.Sleep(20)
End If
End Sub
Public Sub EnsureHandleIsCreated(ByRef c As Control)
Try
If Not c.IsHandleCreated Then
Dim h As System.IntPtr = c.Handle()
End If
If c.HasChildren Then
For Each child As Control In c.Controls
Try
EnsureHandleIsCreated(child)
Catch ex As Exception
DAL.LogException(ex.Message, ex.StackTrace, "EnsureHandleIsCreated: " & c.Name, 0)
End Try
Next
End If
Catch ex As Exception
DAL.LogException(ex.Message, ex.StackTrace, "EnsureHandleIsCreated: " & c.Name, 0)
End Try
End Sub
Private Sub frmMain_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
lbRole.Text = RoleName
lbName.Text = UserName
Try
Me.Activate()
If Thread.CurrentThread.IsBackground Then
Throw New ApplicationException(THREAD_IS_INVALID)
End If
Thread.CurrentThread.Name = THREAD_MAIN_NAME
CheckForInvalidThread()
clGlobals.frmMain = Me
If RoleName Is Nothing Or String.IsNullOrEmpty(RoleName) Or RoleName.Equals("OPERATOR", StringComparison.InvariantCultureIgnoreCase) Then
tcEtch.TabPages("tbMaintenance").Hide()
tcEtch.TabPages("tbAdmin").Hide()
End If
Catch ex As Exception
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
Exit Sub
Finally
End Try
Initiate()
End Sub
Public Sub GoToNextStep() Handles Me.GoToNextStepEvent
Try
CheckForInvalidThread()
CurrentStep = CurrentStep + 1
Select Case CurrentStep
Case 0 To 2
If Me.InvokeRequired Then
_delegateDisplayInitiate = AddressOf DisplayStep2
Me.Invoke(_delegateDisplayInitiate)
Else
DisplayStep2()
End If
Case 3
If ucCycleStart.InvokeRequired Then
_delegateDisplayInitiate = AddressOf DisplayStep3
ucCycleStart.Invoke(_delegateDisplayInitiate)
Else
DisplayStep3()
End If
Case 4
If Me.InvokeRequired Or ucPartCountVerification.InvokeRequired Or Thread.CurrentThread.IsBackground Then
Throw New Exception("Check out")
End If
EnsureHandleIsCreated(ucPartCountVerification)
If ucPartCountVerification.InvokeRequired Then
_delegateDisplayInitiate = AddressOf DisplayStep4
ucPartCountVerification.Invoke(_delegateDisplayInitiate)
Else
DisplayStep4()
End If
End Select
Catch ex As Exception
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
End Sub
Private Sub DisplayStep4() Handles Me.DisplayStep4Event
ucPartCountVerification.Visible = True
ucPartCountVerification.Show()
ucPartCountVerification.Initiate()
End Sub
Public Sub Initiate()
frmMain.CheckForInvalidThread()
'Just to verify if things are fine.. i put in this check below
If Me.InvokeRequired Or pnStep4.InvokeRequired Or Not (Me.IsHandleCreated And pnStep4.IsHandleCreated ) Then
MessageBox.Show("cHECK OUT")
Else
Me.Visible = True
pnStep4.Visible = True
Me.BackColor = Color.Red
pnStep4.BackColor = Color.Gray
Dim height = Me.Size.Height
Dim width = Me.Size.Width
MessageBox.Show(height.ToString() + Me.InvokeRequired.ToString())
End If
end Sub
You certainly can raise events from a BackgroundWorker or Timer. You're already checking InvokeRequired, which is the correct thing to do. You need to then call BeginInvoke (or Invoke, if it needs to be synchronous) to update your UI.
You are on the right track. When dealing with the events that come from some libraries, you cannot be certain on which thread they are delivered. That is disappointing because Windows Forms has the restriction that call calls be made from the GUI thread. This MSDN article explains the problem.
You are on the right track with InvokeRequired. It lets you see whether you are on the right thread, but you need a way to handle this case and re-invkoe the event on the proper thread.
Here is how I do this in C#...
public delegate void uiEventHandler();
void uiEvent(object sender, EventArgs e)
{
if (InvokeRequired)
{
// We are not on the correct thread. We'd better get there.
var eh = new uiEventHandler(uiEvent);
Invoke(eh, new object[] { sender, e });
return; //This thread has no more work to do.
}
// Do your work here that requires being performed on the GUI thread.
}