GOTCHA 61 Exceptions thrown from threads in the pool are lost


GOTCHA #61 Exceptions thrown from threads in the pool are lost

If 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 exception

C# (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-23


Why 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 zero


However, 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-28


IN A NUTSHELL

When 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 ALSO

Gotcha #6, "Exceptions may go unhandled" and Gotcha #58, "Threads from the thread pool are scarce."



    .NET Gotachas
    .NET Gotachas
    ISBN: N/A
    EAN: N/A
    Year: 2005
    Pages: 126

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net