如何:接收第一机会异常通知

注意

本文特定于 .NET Framework。 它不适用于 .NET 的较新版本实现,包括 .NET 6 及更高版本。

通过 AppDomain 类的 FirstChanceException 事件,可在公共语言运行时开始搜索异常处理程序之前,收到已引发异常的通知。

该事件在应用程序域级别引发。 由于执行线程可能经过多个应用程序域,因此可在一个应用程序域中处理另一个应用程序域中未处理的异常。 已添加该事件的处理程序的每个应用程序域中均会出现通知,直到某个应用程序域处理该异常。

本文中的过程和示例说明如何在具有一个应用程序域的简单程序中和创建的应用程序域中接收最可能发生的异常通知。

有关跨多个应用程序域的更复杂示例,请参阅 FirstChanceException 事件的示例。

在默认应用程序域中接收最可能发生的异常通知

在以下过程中,应用程序的入口点(Main() 方法)在默认应用程序域中运行。

在默认应用程序域中演示最可能发生的异常通知

  1. 使用 lambda 函数为 FirstChanceException 事件定义事件处理程序,并将其附加到此事件。 在此示例中,事件处理程序输出了在其中处理事件的应用程序域的名称和异常的 Message 属性。

    using System;
    using System.Runtime.ExceptionServices;
    
    class Example
    {
        static void Main()
        {
            AppDomain.CurrentDomain.FirstChanceException +=
                (object source, FirstChanceExceptionEventArgs e) =>
                {
                    Console.WriteLine("FirstChanceException event raised in {0}: {1}",
                        AppDomain.CurrentDomain.FriendlyName, e.Exception.Message);
                };
    
    Imports System.Runtime.ExceptionServices
    
    Class Example
    
        Shared Sub Main()
    
            AddHandler AppDomain.CurrentDomain.FirstChanceException,
                       Sub(source As Object, e As FirstChanceExceptionEventArgs)
                           Console.WriteLine("FirstChanceException event raised in {0}: {1}",
                                             AppDomain.CurrentDomain.FriendlyName,
                                             e.Exception.Message)
                       End Sub
    
  2. 引发异常并将其捕获。 在运行时找到异常处理程序之前,引发 FirstChanceException 事件并显示一条消息。 此消息后跟由异常处理程序显示的消息。

    try
    {
        throw new ArgumentException("Thrown in " + AppDomain.CurrentDomain.FriendlyName);
    }
    catch (ArgumentException ex)
    {
        Console.WriteLine("ArgumentException caught in {0}: {1}",
            AppDomain.CurrentDomain.FriendlyName, ex.Message);
    }
    
    Try
        Throw New ArgumentException("Thrown in " & AppDomain.CurrentDomain.FriendlyName)
    
    Catch ex As ArgumentException
    
        Console.WriteLine("ArgumentException caught in {0}: {1}",
            AppDomain.CurrentDomain.FriendlyName, ex.Message)
    End Try
    
  3. 引发异常,但不将其捕获。 在运行时查找异常处理程序之前,引发 FirstChanceException 事件并显示一条消息。 由于没有任何异常处理程序,因此应用程序将终止。

            throw new ArgumentException("Thrown in " + AppDomain.CurrentDomain.FriendlyName);
        }
    }
    
            Throw New ArgumentException("Thrown in " & AppDomain.CurrentDomain.FriendlyName)
        End Sub
    End Class
    

    此过程前 3 个步骤中显示的代码构成了一个完整的控制台应用程序。 应用程序的输出因 .exe 文件的名称而异,因为默认应用程序域的名称是由 .exe 文件的名称和扩展名组成的。 有关示例输出,请参阅以下内容。

    /* This example produces output similar to the following:
    
    FirstChanceException event raised in Example.exe: Thrown in Example.exe
    ArgumentException caught in Example.exe: Thrown in Example.exe
    FirstChanceException event raised in Example.exe: Thrown in Example.exe
    
    Unhandled Exception: System.ArgumentException: Thrown in Example.exe
       at Example.Main()
     */
    
    ' This example produces output similar to the following:
    '
    'FirstChanceException event raised in Example.exe: Thrown in Example.exe
    'ArgumentException caught in Example.exe: Thrown in Example.exe
    'FirstChanceException event raised in Example.exe: Thrown in Example.exe
    '
    'Unhandled Exception: System.ArgumentException: Thrown in Example.exe
    '   at Example.Main()
    

在另一个应用程序域中接收最可能发生的异常通知

如果程序包含多个应用程序域,则可选择用于接收通知的应用程序域。

在创建的应用程序域中接收最可能发生的异常通知

  1. FirstChanceException 事件定义事件处理程序。 此示例使用 static 方法(在 Visual Basic 中为 Shared 方法),该方法可输出在其中处理事件的应用程序域的名称和异常的 Message 属性。

    static void FirstChanceHandler(object source, FirstChanceExceptionEventArgs e)
    {
        Console.WriteLine("FirstChanceException event raised in {0}: {1}",
            AppDomain.CurrentDomain.FriendlyName, e.Exception.Message);
    }
    
    Shared Sub FirstChanceHandler(ByVal source As Object,
                                  ByVal e As FirstChanceExceptionEventArgs)
    
        Console.WriteLine("FirstChanceException event raised in {0}: {1}",
            AppDomain.CurrentDomain.FriendlyName, e.Exception.Message)
    End Sub
    
  2. 创建一个应用程序域,并为该应用程序域的 FirstChanceException 事件添加事件处理程序。 在此示例中,该应用程序域的名称为 AD1

    AppDomain ad = AppDomain.CreateDomain("AD1");
    ad.FirstChanceException += FirstChanceHandler;
    
    Dim ad As AppDomain = AppDomain.CreateDomain("AD1")
    AddHandler ad.FirstChanceException, AddressOf FirstChanceHandler
    

    可按照相同的方式在默认应用程序域中处理此事件。 使用 Main() 中的 static(在 Visual Basic 中为 SharedAppDomain.CurrentDomain 属性可获取对默认应用程序域的引用。

在应用程序域中演示最可能发生的异常通知

  1. 在上一过程创建的应用程序域中创建一个 Worker 对象。 Worker 类必须是公共的,且派生自 MarshalByRefObject,如本文末尾的完整示例中所示。

    Worker w = (Worker) ad.CreateInstanceAndUnwrap(
                            typeof(Worker).Assembly.FullName, "Worker");
    
    Dim w As Worker = CType(ad.CreateInstanceAndUnwrap(
                                GetType(Worker).Assembly.FullName, "Worker"),
                            Worker)
    
  2. 调用 Worker 对象的一个可引发异常的方法。 在此示例中,调用了两次 Thrower 方法。 第一次调用时,该方法的参数为 true,这使该方法捕获自己的异常。 第二次调用时,该方法的参数为 false,并且 Main() 方法捕获默认应用程序域中的异常。

    // The worker throws an exception and catches it.
    w.Thrower(true);
    
    try
    {
        // The worker throws an exception and doesn't catch it.
        w.Thrower(false);
    }
    catch (ArgumentException ex)
    {
        Console.WriteLine("ArgumentException caught in {0}: {1}",
            AppDomain.CurrentDomain.FriendlyName, ex.Message);
    }
    
    ' The worker throws an exception and catches it.
    w.Thrower(true)
    
    Try
        ' The worker throws an exception and doesn't catch it.
        w.Thrower(false)
    
    Catch ex As ArgumentException
    
        Console.WriteLine("ArgumentException caught in {0}: {1}",
            AppDomain.CurrentDomain.FriendlyName, ex.Message)
    End Try
    
  3. Thrower 方法中放置代码以控制该方法是否处理自己的异常。

    if (catchException)
    {
        try
        {
            throw new ArgumentException("Thrown in " + AppDomain.CurrentDomain.FriendlyName);
        }
        catch (ArgumentException ex)
        {
            Console.WriteLine("ArgumentException caught in {0}: {1}",
                AppDomain.CurrentDomain.FriendlyName, ex.Message);
        }
    }
    else
    {
        throw new ArgumentException("Thrown in " + AppDomain.CurrentDomain.FriendlyName);
    }
    
    If catchException
    
        Try
            Throw New ArgumentException("Thrown in " & AppDomain.CurrentDomain.FriendlyName)
    
        Catch ex As ArgumentException
    
            Console.WriteLine("ArgumentException caught in {0}: {1}",
                AppDomain.CurrentDomain.FriendlyName, ex.Message)
        End Try
    Else
    
        Throw New ArgumentException("Thrown in " & AppDomain.CurrentDomain.FriendlyName)
    End If
    

示例

下例创建一个名为 AD1 的应用程序域,并向该应用程序域的 FirstChanceException 事件添加一个事件处理程序。 此示例还会在应用程序域中创建 Worker 类的一个实例,并调用名为 Thrower 的方法,该方法将引发 ArgumentException。 该方法可能会捕获异常,也可能无法处理异常,具体取决于其参数的值。

每当 Thrower 方法在 AD1 中引发异常时,AD1 中就会引发 FirstChanceException 事件,并且事件处理程序会显示一条消息。 然后,运行时将查找异常处理程序。 在第一种情况下,可在 AD1 中找到异常处理程序。 在第二种情况下,不会在 AD1 中处理异常,而是在默认应用程序域中捕获异常。

注意

默认应用程序域的名称与可执行文件的名称相同。

如果将 FirstChanceException 事件的处理程序添加到默认应用程序域,则在默认应用程序域处理异常之前,系统将引发并处理该事件。 为此,请将 C# 代码 AppDomain.CurrentDomain.FirstChanceException += FirstChanceException;(在 Visual Basic 中为 AddHandler AppDomain.CurrentDomain.FirstChanceException, FirstChanceException)添加到 Main() 的开头。

using System;
using System.Reflection;
using System.Runtime.ExceptionServices;

class Example
{
    static void Main()
    {
        // To receive first chance notifications of exceptions in
        // an application domain, handle the FirstChanceException
        // event in that application domain.
        AppDomain ad = AppDomain.CreateDomain("AD1");
        ad.FirstChanceException += FirstChanceHandler;

        // Create a worker object in the application domain.
        Worker w = (Worker) ad.CreateInstanceAndUnwrap(
                                typeof(Worker).Assembly.FullName, "Worker");

        // The worker throws an exception and catches it.
        w.Thrower(true);

        try
        {
            // The worker throws an exception and doesn't catch it.
            w.Thrower(false);
        }
        catch (ArgumentException ex)
        {
            Console.WriteLine("ArgumentException caught in {0}: {1}",
                AppDomain.CurrentDomain.FriendlyName, ex.Message);
        }
    }

    static void FirstChanceHandler(object source, FirstChanceExceptionEventArgs e)
    {
        Console.WriteLine("FirstChanceException event raised in {0}: {1}",
            AppDomain.CurrentDomain.FriendlyName, e.Exception.Message);
    }
}

public class Worker : MarshalByRefObject
{
    public void Thrower(bool catchException)
    {
        if (catchException)
        {
            try
            {
                throw new ArgumentException("Thrown in " + AppDomain.CurrentDomain.FriendlyName);
            }
            catch (ArgumentException ex)
            {
                Console.WriteLine("ArgumentException caught in {0}: {1}",
                    AppDomain.CurrentDomain.FriendlyName, ex.Message);
            }
        }
        else
        {
            throw new ArgumentException("Thrown in " + AppDomain.CurrentDomain.FriendlyName);
        }
    }
}

/* This example produces output similar to the following:

FirstChanceException event raised in AD1: Thrown in AD1
ArgumentException caught in AD1: Thrown in AD1
FirstChanceException event raised in AD1: Thrown in AD1
ArgumentException caught in Example.exe: Thrown in AD1
 */
Imports System.Reflection
Imports System.Runtime.ExceptionServices

Class Example
    Shared Sub Main()

        ' To receive first chance notifications of exceptions in 
        ' an application domain, handle the FirstChanceException
        ' event in that application domain.
        Dim ad As AppDomain = AppDomain.CreateDomain("AD1")
        AddHandler ad.FirstChanceException, AddressOf FirstChanceHandler


        ' Create a worker object in the application domain.
        Dim w As Worker = CType(ad.CreateInstanceAndUnwrap(
                                    GetType(Worker).Assembly.FullName, "Worker"),
                                Worker)

        ' The worker throws an exception and catches it.
        w.Thrower(true)

        Try
            ' The worker throws an exception and doesn't catch it.
            w.Thrower(false)

        Catch ex As ArgumentException

            Console.WriteLine("ArgumentException caught in {0}: {1}",
                AppDomain.CurrentDomain.FriendlyName, ex.Message)
        End Try
    End Sub

    Shared Sub FirstChanceHandler(ByVal source As Object,
                                  ByVal e As FirstChanceExceptionEventArgs)

        Console.WriteLine("FirstChanceException event raised in {0}: {1}",
            AppDomain.CurrentDomain.FriendlyName, e.Exception.Message)
    End Sub
End Class

Public Class Worker
    Inherits MarshalByRefObject

    Public Sub Thrower(ByVal catchException As Boolean)

        If catchException

            Try
                Throw New ArgumentException("Thrown in " & AppDomain.CurrentDomain.FriendlyName)

            Catch ex As ArgumentException

                Console.WriteLine("ArgumentException caught in {0}: {1}",
                    AppDomain.CurrentDomain.FriendlyName, ex.Message)
            End Try
        Else

            Throw New ArgumentException("Thrown in " & AppDomain.CurrentDomain.FriendlyName)
        End If
    End Sub
End Class

' This example produces output similar to the following:
'
'FirstChanceException event raised in AD1: Thrown in AD1
'ArgumentException caught in AD1: Thrown in AD1
'FirstChanceException event raised in AD1: Thrown in AD1
'ArgumentException caught in Example.exe: Thrown in AD1

请参阅