Jump to content
xisto Community
turbopowerdmaxsteel

Vb.net: How To Trap Exceptions In Invoked Methods?

Recommended Posts

I am having some trouble in trapping the exceptions raised by Invoked methods. By Invoked, I mean the methods that have been Invoked by the Control.Invoke method. Given below is a simple example of this problem.

 

I have a Form named Form1 which contains an object Tim of the Timer class. Unlike the System.Windows.Forms.Timer object, the Timer class (FullName: System.Timers.Timer) raises the Elapsed event on a seperate thread. I think the Timer object does the same thing but synchronizes the event by raising it on the same thread as that of the Form it is in.

 

When I try to execute the following code, an exception: Cross threaded operation not valid is thrown.

 

Public Class Form1	Dim Tim As New System.Timers.Timer	Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load		Tim.Interval = 1000		AddHandler Tim.Elapsed, AddressOf Tim_Elapsed		Tim.Enabled = True	End Sub	Sub Tim_Elapsed(ByVal Sender As Object, ByVal e As System.Timers.ElapsedEventArgs)		Me.Text = "The Clock Just Ticked"   ' This Line Causes the Exception	End SubEnd Class

This is because the Tim_Elapsed method is being invoked on a seperate thread, the one belonging to the object Tim. So, I used the mechanism stated in MSDN and came up with the following code:-

 

Public Class Form1	Dim Tim As New System.Timers.Timer	Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load		Tim.Interval = 1000		AddHandler Tim.Elapsed, AddressOf Tim_Elapsed		Tim.Enabled = True	End Sub	Sub Tim_Elapsed(ByVal Sender As Object, ByVal e As System.Timers.ElapsedEventArgs)		Dim D As New MyDelegate(AddressOf MyMethod)		Me.Invoke(D)	End Sub	Delegate Sub MyDelegate()	Sub MyMethod()		Me.Text = "The Clock Just Ticked"	End SubEnd Class

All things seem to be fine, but when the delegate method raises an exception, say an overflow exception, the exception is traced on the line which invokes this method rather than the line which raises the exception - I = I / 0.

 

Sub MyMethod()		Dim I As Integer = 5		I = I / 0  ' The culprit Line		Me.Text = "The Clock Just Ticked"End Sub

Posted Image

 

This problem raises havoc when trying to debug larger implementation of this concept. Any ideas on how to solve this problem?

Share this post


Link to post
Share on other sites

You have come across one of my biggest headaches in .NET, cross threading exceptions. I usually use a little more sophisticated logic to determine if an invoke is required or not. The following code either enables or disables a control.

#Region "Threaded Callbacks"	Delegate Sub txtEnableDelegate(ByVal bEnabled As Boolean, ByVal ctl As Control)	Dim dtxtEnable As New txtEnableDelegate(AddressOf txtEnable)	Private Sub txtEnable(ByVal bEnabled As Boolean, ByVal ctl As Control)		If ctl.InvokeRequired Then			Dim d As New txtEnableDelegate(AddressOf txtEnable)			Me.Invoke(d, New Object() {bEnabled, ctl})		Else			ctl.Enabled = bEnabled		End If	End Sub#End Region

and it is called by

dtxtEnable(True, udCaptureRate)

Another trick that I use is threading.timer. I find that it is more flexible when you are starting a new thread and want a timer built-in. If this does not help then you may want to consider making your own exception class specific that your thread. You can throw a general or your own exception when it is needed and to my understanding exceptions will automatically be invoked.

A second approach would to make your object to be threaded a control itself. That way you can call the control.invoke(delegate) from the thread and not have all the logic needed to invoke the individual controls. The trick is to pass the delegate as a property to the thread when it is created. There is a good example of this in YCC Bot Maker http://forums.xisto.com/no_longer_exists/

I hop this help and let me know if it still doesnât do the trick. I am interested in your results myself.

Share this post


Link to post
Share on other sites

This problem got me to thinking more. I wrote a test program that checked to see if exceptions are automatically invoked onto the correct thread and they are. As long as the code that may cause the exception is in a try catch statement you should not have any problems. The code below demonstrates this. The main form only has a textbox named txtOutput which is multiline and has scrollbars turned on. You will notice that the textbox does not have any invoke statements yet still handles the text coming out of the exception even though it is raised on another thread. This is because an exception will automatically jump to the calling thread until it reaches the lowest level thread. In this case it is the main GUI thread.

Option Strict OnOption Explicit OnPublic Class frmMain	Private Sub frmMain_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load		Dim Tim As New System.Windows.Forms.Timer		Tim.Interval = 1000		AddHandler Tim.Tick, AddressOf Tim_Elapsed		Tim.Enabled = True	End Sub	Sub Tim_Elapsed(ByVal Sender As Object, ByVal e As System.EventArgs)		Try			Dim I As Integer = 5			I = CInt(I / 0)  ' The culprit Line			Me.txtOutput.Text += Date.Now.ToString + "The Clock Just Ticked" + vbCrLf		Catch ex As Exception			Me.txtOutput.Text += ex.ToString + vbCrLf		End Try	End SubEnd Class

Share this post


Link to post
Share on other sites

I used to utilize the Invoke Required flow block as well. But it is kind of useless, because in my case, everytime invoke is required. You have utilized the System.Windows.Forms.Timer component in your code. The elapsed event is automatically raised on the same thread as that of the form it is in. So, the exceptions in it are properly traced. If you remove the line I = Cint(I / 0) from the block, Me.txtOutput.text + = .... will not throw an exception. While, if the System.Timers.Timer class was used, it would throw the Cross threaded operation not valid exception.

 

My problem is not specific to the Timer control itself. I used the Timer class for simplicity. Actually, I am using asynchronous operations on a Socket class and wrapping it up on a custom Socket component which behaves similar to the old VB6 Socket.

Share this post


Link to post
Share on other sites

Sorry about that, I was being a bonehead and rushed out the answer right before I left work. I of all people should have caught that one.

My traditional approach is to just put invokes on every single control that interacts with a threaded return. It can become time consuming and nasty but I just put them in a separate region and forget about them. In my experience there are actually only a few controls that will need invokes. The current program that I am working on has the same problem but the only controls affected are buttons that need to change their enabled status and a list view. I wrote a single delegate to take care of the controls (from the previous example) and two for the list view as there were only two operations involved.

I understand that there may be several threads flying around in your main class but it really doesnât matter. The only time that you need to worry is with windows controls.

Below is the second alternative example that I mentioned before. You can create a separate class and have it inherit control. Since you are making your own socket class I assume this would be the best anyway as you can just drop it on your form. As you can see I have created two delegates on frmMain which are passed into the control as a property. Once you want output from the control, the invoke method is called with the proper delegate. The delegates are put back on the main thread for you.

You can also grab the full example project from http://forums.xisto.com/no_longer_exists/. Once again let me know if this helps.

The main form

Option Strict OnOption Explicit OnPublic Class frmMain	Private _work As New WorkComponent#Region "Threaded Callbacks"	Delegate Sub WorkComponent_ReturnDelegate(ByVal strMessage As String)	Delegate Sub WorkComponent_ExceptionDelegate(ByVal e As Exception)	Dim WorkComponent_ReturnDeleg As New WorkComponent_ReturnDelegate(AddressOf WorkComponent_Return)	Dim WorkComponent_ExceptionDeleg As New WorkComponent_ExceptionDelegate(AddressOf WorkComponent_Exception)#End Region	Private Sub frmMain_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load		'setup work		'You have to create the handle first		Dim iCMDHandle As IntPtr = _work.Handle		_work.dReturn = WorkComponent_ReturnDeleg		_work.dException = WorkComponent_ExceptionDeleg		Dim Tim As New System.Timers.Timer		Tim.Interval = 1000		AddHandler Tim.Elapsed, AddressOf Tim_Elapsed		Tim.Enabled = True	End Sub	Sub Tim_Elapsed(ByVal Sender As Object, ByVal e As System.Timers.ElapsedEventArgs)		_work.WorkToBeDone()	End Sub	Private Sub WorkComponent_Return(ByVal strMessage As String)		txtOutput.Text += strMessage + vbCrLf	End Sub	Private Sub WorkComponent_Exception(ByVal e As Exception)		txtOutput.Text += e.ToString + vbCrLf	End SubEnd Class

The control class

Option Strict OnOption Explicit OnPublic Class WorkComponent	Inherits Control	Private _dReturn As frmMain.WorkComponent_ReturnDelegate	Private _dException As frmMain.WorkComponent_ExceptionDelegate	Private _iCount As Integer#Region "Properties"	Public Property dReturn() As frmMain.WorkComponent_ReturnDelegate		Get			Return _dReturn		End Get		Set(ByVal Value As frmMain.WorkComponent_ReturnDelegate)			_dReturn = Value		End Set	End Property	Public Property dException() As frmMain.WorkComponent_ExceptionDelegate		Get			Return _dException		End Get		Set(ByVal Value As frmMain.WorkComponent_ExceptionDelegate)			_dException = Value		End Set	End Property#End Region	Public Sub WorkToBeDone()		_iCount += 1		Try			'exception thrown every other time			If _iCount Mod 2 = 0 Then				Dim I As Integer = 5				I = CInt(I / 0)  ' The culprit Line			End If			BeginInvoke(_dReturn, New Object() {"Work Done"})		Catch ex As Exception			BeginInvoke(_dException, New Object() {ex})		End Try	End SubEnd Class

P.S. To bad code doesnât count for post credits, I am quite proud of this one, lol.

P.P.S Try not to poke a hole in this idea :rolleyes:

Share this post


Link to post
Share on other sites

The WorkComponent_Return method is where our custom code would go, right? In that case, the Arithmetic Exception should be raised there. Again, in this scenario, the error line cannot be traced.

 

The following is a simple example of what happens in my Winsock component.

 

Imports System.Net.SocketsPublic Class Winsock	Dim Sck As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)	Sub Connect(ByVal Host As String, ByVal Port As Integer)		Dim D As New System.AsyncCallback(AddressOf ConnectCallback)		Sck.BeginConnect(Host, Port, D, Sck)	End Sub	Public Event SocketConnected()	Sub ConnectCallback(ByVal ar As IAsyncResult)		Sck.EndConnect(ar)		_SyncObject.Invoke(New ConnectedEventRaiserDelegate(AddressOf ConnectedEventRaiser))	End Sub	Delegate Sub ConnectedEventRaiserDelegate()	Sub ConnectedEventRaiser()		RaiseEvent SocketConnected()	End Sub	Public _SyncObject As Control	Public Property SyncObject() As Control		Get			Return _SyncObject		End Get		Set(ByVal value As Control)			_SyncObject = value		End Set	End PropertyEnd Class

The Winsock control contains a System.Net.Sockets.Socket object for the asynchronous network activities such as connecting to host, sending/reciving data, etc. It generates events such as SocketConnected, DataArrival, etc. These are analogous to their VB6 counterparts. Now, the trouble is, that the event, say SocketConnected is raised from the Delegate which ends the asynchronous operation, in this case, ConnectCallback. The main form which handles this event cannot make any cross threaded operations inside this event handler unless the event is raised on the same thread. So, I am using the invoke method to invoke a method on the main form's thread. This method in turn raises the SocketConnected event. Now the event handler in the main form can do jobs such as changing the text of the Form to something like - "Socket Connected". This confirms that the event is being raised on the same thread as that of the main form. However, any exception in this event handler is traced to the line which invoked the method (_SyncObject.Invoke(New ConnectedEventRaiserDelegate(AddressOf ConnectedEventRaiser))) and not to the culprit line in the event handler.

 

(To generalize this effect, I am using the Sync object property which specifies the form on whose thread the event needs to be raised.)

 

Here's the code for the main Form:-

 

Public Class frmMain	Dim WithEvents Sck As New Winsock()	Private Sub frmMain_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load		Sck.SyncObject = Me ' This allows the Invoke method to be called on this Form		Sck.Connect("microsoft.com;, 80)	End Sub	Private Sub Sck_SocketConnected() Handles Sck.SocketConnected		Me.Text = "Socket Connected"	' To Make Sure Event is Raised on the same thread		Dim I As Integer = 5		I = I / 0   ' Culprit Line	End SubEnd Class

Socket Designer code. This is the default code generated when you add a component:-

 

Partial Class Winsock	Inherits System.ComponentModel.Component	<System.Diagnostics.DebuggerNonUserCode()> _	Public Sub New(ByVal container As System.ComponentModel.IContainer)		MyClass.New()		'Required for Windows.Forms Class Composition Designer support		If (container IsNot Nothing) Then			container.Add(Me)		End If	End Sub	<System.Diagnostics.DebuggerNonUserCode()> _	Public Sub New()		MyBase.New()		'This call is required by the Component Designer.		InitializeComponent()	End Sub	'Component overrides dispose to clean up the component list.	<System.Diagnostics.DebuggerNonUserCode()> _	Protected Overrides Sub Dispose(ByVal disposing As Boolean)		Try			If disposing AndAlso components IsNot Nothing Then				components.Dispose()			End If		Finally			MyBase.Dispose(disposing)		End Try	End Sub	'Required by the Component Designer	Private components As System.ComponentModel.IContainer	'NOTE: The following procedure is required by the Component Designer	'It can be modified using the Component Designer.	'Do not modify it using the code editor.	<System.Diagnostics.DebuggerStepThrough()> _	Private Sub InitializeComponent()		components = New System.ComponentModel.Container()	End SubEnd Class

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×
×
  • Create New...

Important Information

Terms of Use | Privacy Policy | Guidelines | We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.