turbopowerdmaxsteel 0 Report post Posted November 15, 2007 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 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
tansqrx 0 Report post Posted November 15, 2007 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 bydtxtEnable(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
tansqrx 0 Report post Posted November 15, 2007 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
turbopowerdmaxsteel 0 Report post Posted November 16, 2007 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
tansqrx 0 Report post Posted November 17, 2007 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 classOption 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 Share this post Link to post Share on other sites
turbopowerdmaxsteel 0 Report post Posted November 17, 2007 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