|
|
|
| Web Hosting Guide |
![]() ![]() |
Vb.net: How To Trap Exceptions In Invoked Methods? |
Nov 15 2007, 10:11 AM
Post
#1
|
|
|
Premium Member Group: [HOSTED] Posts: 447 Joined: 16-February 06 From: Kolkata, India Member No.: 11,322 myCENTs:81.67 |
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. 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) Me.Text = "The Clock Just Ticked" ' This Line Causes the Exception End Sub End 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:- 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 Sub End 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. CODE Sub MyMethod() Dim I As Integer = 5 I = I / 0 ' The culprit Line Me.Text = "The Clock Just Ticked" End Sub ![]() This problem raises havoc when trying to debug larger implementation of this concept. Any ideas on how to solve this problem? |
|
|
|
Nov 15 2007, 10:46 PM
Post
#2
|
|
|
Super Member Group: [HOSTED] Posts: 692 Joined: 25-April 05 Member No.: 4,374 myCENTs:17.73 |
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.
CODE #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 CODE 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://ycoderscookbook.com/YCC_Bot_Maker.htm I hop this help and let me know if it still doesn’t do the trick. I am interested in your results myself. |
|
|
|
Nov 15 2007, 11:33 PM
Post
#3
|
|
|
Super Member Group: [HOSTED] Posts: 692 Joined: 25-April 05 Member No.: 4,374 myCENTs:17.73 |
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.
CODE Option Strict On
Option Explicit On Public 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 Sub End Class |
|
|
|
Nov 16 2007, 02:12 AM
Post
#4
|
|
|
Premium Member Group: [HOSTED] Posts: 447 Joined: 16-February 06 From: Kolkata, India Member No.: 11,322 myCENTs:81.67 |
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. |
|
|
|
Nov 17 2007, 07:12 AM
Post
#5
|
|
|
Super Member Group: [HOSTED] Posts: 692 Joined: 25-April 05 Member No.: 4,374 myCENTs:17.73 |
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://www.ycoderscookbook.com/Files/ThreadException.rar. Once again let me know if this helps. The main form CODE Option Strict On Option Explicit On Public 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 Sub End Class The control class CODE Option Strict On Option Explicit On Public 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 Sub End 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 |
|
|
|
Nov 17 2007, 02:42 PM
Post
#6
|
|
|
Premium Member Group: [HOSTED] Posts: 447 Joined: 16-February 06 From: Kolkata, India Member No.: 11,322 myCENTs:81.67 |
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. CODE Imports System.Net.Sockets Public 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 Property End 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:- CODE 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("www.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 Sub End Class Socket Designer code. This is the default code generated when you add a component:- CODE 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 Sub End Class |
|
|
|
![]() ![]() |
1 User(s) are reading this topic (1 Guests and 0 Anonymous Users)
0 Members:
Similar Topics
| Topic Title | Replies | Topic Starter | Views | Last Action | |||
|---|---|---|---|---|---|---|---|
![]() |
8 | LegallyHigh | 1,072 | 12th May 2008 - 10:22 PM Last post by: xboxrulz |
|||
![]() |
0 | turbopowerdmaxsteel | 2,151 | 3rd August 2007 - 04:19 AM Last post by: turbopowerdmaxsteel |
|||
![]() |
6 | miCRoSCoPiC^eaRthLinG | 1,673 | 5th February 2007 - 08:32 AM Last post by: miCRoSCoPiC^eaRthLinG |
|||
![]() |
5 | CaptainRon | 1,403 | 1st February 2007 - 07:06 PM Last post by: miCRoSCoPiC^eaRthLinG |
|||
![]() |
5 | lonebyrd | 1,468 | 11th October 2006 - 07:21 AM Last post by: BuffaloHELP |
|||
![]() |
1 | Trap | 650 | 25th September 2006 - 10:39 AM Last post by: pyost |
|||
![]() |
0 | Omkar™ | 462 | 9th July 2006 - 11:08 AM Last post by: Omkar™ |
|||
![]() |
2 | WaCo | 1,332 | 27th August 2005 - 08:31 PM Last post by: qwijibow |
|||
![]() |
3 | qwijibow | 1,812 | 20th April 2005 - 04:55 AM Last post by: miCRoSCoPiC^eaRthLinG |
|||
![]() |
6 | friso | 1,304 | 22nd February 2005 - 09:14 PM Last post by: NilsC |
|||
|
Lo-Fi Version | Time is now: 21st March 2010 - 10:36 AM |
© 2010 AstaHost: Free Web Hosting & Technical Discussion, Free Web Hosting. a member of xisto.
Powered by Invision Board. Skin: IPB Forum Skins
Expand / Collapse Navigation



Nov 15 2007, 10:11 AM






