I recently read Jason Clark's excellent article on Unhandled Exceptions
(http://msdn.microsoft.com/msdnmag/is...T/default.aspx) and have
attempted to incorporate the features he talks about in a new application I'm
writing. However, when I try to use ThreadStart to do some work in a separate
thread from my GUI, the methods Jason described don't seem to catch the
exception. Take the following source code:
Public Class UnhandledExceptions
Public Shared Sub Main()
Try
SubMain()
Catch lobjException As Exception
HandleException(lobjException)
End Try
End Sub
Private Shared Sub SubMain()
'ATTACH EVENT HANDLER FOR CLR UNHANDLED EXCEPTIONS
AddHandler AppDomain.CurrentDomain.UnhandledException, AddressOf
AppDomain_UnhandledException
'ATTACH THE EVENT HANDLER FOR UNHANDLED WINDOWS FORMS EXCEPTIONS
AddHandler Application.ThreadException, AddressOf
Application_ThreadException
Application.EnableVisualStyles()
Application.Run(New MainForm)
End Sub
Private Shared Sub HandleException(ByVal vobjException As Exception)
MessageBox.Show(vobjException.Message, "Unhandled Exception Caught",
MessageBoxButtons.OK, MessageBoxIcon.Stop)
Environment.Exit(1)
End Sub
Private Shared Sub AppDomain_UnhandledException(ByVal sender As Object,
ByVal e As UnhandledExceptionEventArgs)
Dim ue As Exception
ue = CType(e.ExceptionObject, Exception)
HandleException(ue)
End Sub
Private Shared Sub Application_ThreadException(ByVal sender As Object,
ByVal e As System.Threading.ThreadExceptionEventArgs)
HandleException(e.Exception)
End Sub
End Class
Public Class MainForm
Inherits System.Windows.Forms.Form
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after the InitializeComponent() call
End Sub
'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
Friend WithEvents Button1 As System.Windows.Forms.Button
Friend WithEvents Button2 As System.Windows.Forms.Button
Friend WithEvents Button3 As System.Windows.Forms.Button
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
Me.Button1 = New System.Windows.Forms.Button
Me.Button2 = New System.Windows.Forms.Button
Me.Button3 = New System.Windows.Forms.Button
Me.SuspendLayout()
'
'Button1
'
Me.Button1.Location = New System.Drawing.Point(44, 24)
Me.Button1.Name = "Button1"
Me.Button1.Size = New System.Drawing.Size(204, 24)
Me.Button1.TabIndex = 0
Me.Button1.Text = "Thread"
'
'Button2
'
Me.Button2.Location = New System.Drawing.Point(44, 60)
Me.Button2.Name = "Button2"
Me.Button2.Size = New System.Drawing.Size(204, 24)
Me.Button2.TabIndex = 1
Me.Button2.Text = "ThreadPool"
'
'Button3
'
Me.Button3.Location = New System.Drawing.Point(44, 96)
Me.Button3.Name = "Button3"
Me.Button3.Size = New System.Drawing.Size(204, 24)
Me.Button3.TabIndex = 2
Me.Button3.Text = "ThreadStart"
'
'MainForm
'
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(292, 273)
Me.Controls.Add(Me.Button3)
Me.Controls.Add(Me.Button2)
Me.Controls.Add(Me.Button1)
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle
Me.MaximizeBox = False
Me.Name = "MainForm"
Me.StartPosition = System.Windows.Forms.FormStartPosition.CenterScree n
Me.Text = "Unhandled Exceptions"
Me.ResumeLayout(False)
End Sub
#End Region
Private Sub DoWork()
Throw New InvalidOperationException
End Sub
Private Sub DoWork(ByVal parameter As Object)
Throw New InvalidOperationException
End Sub
Private Sub WorkComplete(ByVal ar As IAsyncResult)
MessageBox.Show("Work Complete", Me.Text, MessageBoxButtons.OK,
MessageBoxIcon.Information)
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
Dim t As Threading.Thread
t = New Threading.Thread(AddressOf DoWork)
t.Start()
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button2.Click
Threading.ThreadPool.QueueUserWorkItem(New
Threading.WaitCallback(AddressOf DoWork))
End Sub
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button3.Click
Dim ts As System.Threading.ThreadStart
ts = New System.Threading.ThreadStart(AddressOf DoWork)
ts.BeginInvoke(New AsyncCallback(AddressOf WorkComplete), Nothing)
End Sub
End Class
If I click button 1 a new thread is created and it's Start method is called.
This causes DoWork to be called in a new thread, the unhandled exception is
caught and the application can be gracefully shut down (The IDE may step in
and say there's an unhandled exception but if you click Continue the
behaviour described above will occur). If I click button 2 a new work item
is added to the ThreadPool, DoWork is called in a background worker thread
and the unhandled exception is caught as before. If I click button 3 though,
the BeginInvoke method is called on a ThreadStart object causing DoWork to be
called in a new thread, but the unhandled exception seems to be swallowed and
it continues on to call the WorkComplete method.
Can anyone tell me why the third way behaves differently to the first 2? I
want to use the third way as it calls a method when it's finished which is
very useful, but I want to be able to catch any unhandled exceptions.
Thanks for taking the time to read all this and thanks in advance for your
help!
Colin