GOTCHA #61 Exceptions thrown from threads in the pool are lostIf any exceptions are thrown from a thread you create and you don't handle them, the CLR reports them to the user. In a console application, a message is printed on the console. In a Windows application, a dialog appears with the details of the exception. Of course, a program that displays such unhandled exceptions is undesirable. However, you might also agree that such a program is better than a program that continues to execute and quietly misbehaves after things go wrong. Let's look at Example 7-23. Example 7-23. Unhandled exceptionC# (ExceptionInThread) using System; using System.Threading; namespace DelegateThread { class Test { private static void Method1() { Console.WriteLine("Method1 is throwing exception"); throw new ApplicationException("**** oops ****"); } delegate void Method1Delegate(); [STAThread] static void Main(string[] args) { Console.WriteLine("We first use a thread"); Thread aThread = new Thread(new ThreadStart(Method1)); aThread.Start(); Console.WriteLine("press return"); Console.ReadLine(); Console.WriteLine("We will use a Delegate now"); Method1Delegate dlg = new Method1Delegate(Method1); IAsyncResult handle = dlg.BeginInvoke(null, null); Thread.Sleep(1000); Console.WriteLine("Was the exception reported so far?"); try { Console.WriteLine("Let's call EndInvoke"); dlg.EndInvoke(handle); } catch(Exception ex) { Console.WriteLine("Exception: {0}", ex.Message); } Console.WriteLine("press return"); Console.ReadLine(); Console.WriteLine("We will use a timer now"); System.Timers.Timer theTimer = new System.Timers.Timer(1000); theTimer.Elapsed += new System.Timers.ElapsedEventHandler( theTimer_Elapsed); theTimer.Start(); Thread.Sleep(3000); theTimer.Stop(); Console.WriteLine("press return"); Console.ReadLine(); } private static void theTimer_Elapsed( object sender, System.Timers.ElapsedEventArgs e) { Method1(); } } } VB.NET (ExceptionInThread) Imports System.Threading Module Test Private Sub Method1() Console.WriteLine("Method1 is throwing exception") Throw New ApplicationException("**** oops ****") End Sub Delegate Sub Method1Delegate() Public Sub Main() Console.WriteLine("We first use a thread") Dim aThread As New Thread(AddressOf Method1) aThread.Start() Console.WriteLine("press return") Console.ReadLine() Console.WriteLine("We will use a Delegate now") Dim dlg As New Method1Delegate(AddressOf Method1) Dim handle As IAsyncResult = dlg.BeginInvoke(Nothing, Nothing) Thread.Sleep(1000) Console.WriteLine("Was the exception reported so far?") Try Console.WriteLine("Let's call EndInvoke") dlg.EndInvoke(handle) Catch ex As Exception Console.WriteLine("Exception: {0}", ex.Message) End Try Console.WriteLine("press return") Console.ReadLine() Console.WriteLine("We will use a timer now") Dim theTimer As New System.Timers.Timer(1000) AddHandler theTimer.Elapsed, _ New System.Timers.ElapsedEventHandler( _ AddressOf theTimer_Elapsed) theTimer.Start() Thread.Sleep(3000) theTimer.Stop() Console.WriteLine("press return") Console.ReadLine() End Sub Private Sub theTimer_Elapsed( _ ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Method1() End Sub End Module VB.NET (ExceptionInThread) In this example, you have a method Method1() that throws an exception. You first call this method from our own thread. The CLR reports this exception by displaying a message on the console. Then you invoke the method using Method1Delegate.BeginInvoke(). The exception is not caught or reported when it is thrown. It does surface eventually when you call the delegate's EndInvoke(). The worst offender is the Timer, where the exception simply goes unnoticed. The output from the above program is shown in Figure 7-21. Figure 7-21. Output from Example 7-23Why is this important to know? Consider invoking a web service asynchronously. You set up a delegate to it and in the callback, you call EndInvoke(). The problem is that the callback itself is called from a thread in the thread pool. If an exception is raised in the callback, it is never seen. This is illustrated in Examples 7-24 through 7-27, and in Figures 7-22 and 7-23. Example 7-24. Lost exception in asynchronous call, Web service (C#)C# (ExceptionInThread), server-side code //MyService.asmx.cs part of ACSWebService.dll (Web Service) using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Web; using System.Web.Services; namespace ACSWebService { [WebService(Namespace="MyServiceNameSpace")] public class MyService : System.Web.Services.WebService { // ... [WebMethod] public int Method1(int val) { if (val == 0) throw new ApplicationException( "Do not like the input"); return val; } } } Figure 7-22. Main form (Form1) for Web Service client (C#)Example 7-25. Lost exception in asynchronous call, Web service client (C#)C# (ExceptionInThread), client-side code ///Form1.cs part of AWSClient using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Threading; using AWSClient.ACSWebService; namespace AWSClient { public class Form1 : System.Windows.Forms.Form { private static MyService service; // Parts of this file not shown... private void InvokeButton_Click( object sender, System.EventArgs e) { service = new MyService(); service.Credentials = System.Net.CredentialCache.DefaultCredentials; if (AsynchCheckBox.Checked) { CallAsynch(); } else { CallSynch(); } } private static void CallSynch() { int result = service.Method1(1); MessageBox.Show("Result received: " + result); result = service.Method1(0); MessageBox.Show("Result received: " + result); } private static void CallAsynch() { service.BeginMethod1(1, new AsyncCallback(response), service); Thread.Sleep(1000); service.BeginMethod1(0, new AsyncCallback(response), service); Thread.Sleep(1000); } private static void response(IAsyncResult handle) { MyService theService = handle.AsyncState as MyService; int result = theService.EndMethod1(handle); MessageBox.Show("Result received asynchronously " + result); } } } Example 7-26. Lost exception in asynchronous call, Web service (VB.NET)VB.NET (ExceptionInThread), server-side code 'MyService.asmx.vb part of AVBWebService.dll (Web Service) Imports System.Web.Services <System.Web.Services.WebService(Namespace:="MyServiceNameSpace")> _ Public Class MyService Inherits System.Web.Services.WebService '... <WebMethod()> _ Public Function Method1(ByVal val As Integer) As Integer If val = 0 Then Throw New ApplicationException("Do not like the input") End If Return val End Function End Class Figure 7-23. Main form (Form1) for Web Service (VB.NET)Example 7-27. Lost exception in asynchronous call, Web service client (VB.NET)VB.NET (ExceptionInThread), client-side code 'Form1.vb part of AWSClient Imports System.Threading Imports AWSClient.AVBWebService Public Class Form1 Inherits System.Windows.Forms.Form Private Shared service As MyService 'Parts of this file not shown... Private Sub InvokeButton_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles InvokeButton.Click service = New MyService service.Credentials = _ System.Net.CredentialCache.DefaultCredentials If AsynchCheckBox.Checked Then CallAsynch() Else CallSynch() End If End Sub Private Shared Sub CallSynch() Dim result As Integer = service.Method1(1) MessageBox.Show("Result received: " & result) result = service.Method1(0) MessageBox.Show("Result received: " & result) End Sub Private Shared Sub CallAsynch() service.BeginMethod1(1, _ New AsyncCallback(AddressOf response), service) Thread.Sleep(1000) service.BeginMethod1(0, _ New AsyncCallback(AddressOf response), service) Thread.Sleep(1000) End Sub Private Shared Sub response(ByVal handle As IAsyncResult) Dim theService As MyService = _ CType(handle.AsyncState, MyService) Dim result As Integer = theService.EndMethod1(handle) MessageBox.Show("Result received asynchronously " & _ result) End Sub End Class The web service throws an exception if the parameter to Method1() is zero. From a Windows application, you invoke the web service, first sending a value of 1 and then a value of 0. When you invoke the method synchronously with an argument of zero, the CLR displays an exception, as shown in Figure 7-24. Figure 7-24. Output from Example 7-24 for a synchronous call with a parameter of zeroHowever, if you call it asynchronously, no exception is reported. This is because the callback itself executes on a thread pool thread. You might expect the exception to be received when you call EndInvoke() in the response() method. Yes, an exception is raised (a System.Web.Services.Protocols.SoapException, to be precise). But it isn't propagated to you; the CLR suppresses any exception raised from a thread in the thread pool. You can verify this by adding a TRy/catch block around the EndInvoke() call, as shown in Example 7-28. Example 7-28. Catching exception after call to EndInvoke()C# (ExceptionInThread), client-side code private static void response(IAsyncResult handle) { MyService theService = handle.AsyncState as MyService; try { int result = theService.EndMethod1(handle); MessageBox.Show("Result received asynchronously " + result); } catch(System.Web.Services.Protocols.SoapException ex) { MessageBox.Show("Now I got " + ex); } } VB.NET (ExceptionInThread), client-side code Private Shared Sub response(ByVal handle As IAsyncResult) Dim theService As MyService = _ CType(handle.AsyncState, MyService) Try Dim result As Integer = theService.EndMethod1(handle) MessageBox.Show("Result received asynchronously " & _ result) Catch ex As System.Web.Services.Protocols.SoapException MessageBox.Show("Now I got " & ex.ToString()) End Try End Sub Now when you make the call asynchronously, you get the message shown in Figure 7-25. Figure 7-25. Exception reported after change shown in Example 7-28IN A NUTSHELLWhen using threads from the thread pool, pay extra attention to exception handling. Exceptions may not be reported, depending on what type of object you use: Delegate or Timer. SEE ALSOGotcha #6, "Exceptions may go unhandled" and Gotcha #58, "Threads from the thread pool are scarce." |