在.net中未能用trycatch捕獲到的異常處理

作者:Eaglet

      隨着.Net技術的發展,.Net技術被逐漸應用到很多大型的應用軟件項目中。這些項目的規模越來越大,很多項目中除了自己的代碼外還引用了很多第三方的.net組件。同時很多項目又被應用到很多關鍵的部門,軟件系統的穩定性越來越至關重要。由於.Net 框架提供了非常強大的異常處理機制,同時對一些非託管代碼很難控制的系統問題比如指針越界,內存泄漏等提供了很好的解決方案。相比非託管代碼構建的系統,.Net構建的系統更加穩定。不過這並不是說.Net構建的系統就完全無懈可擊,很多由於代碼的不嚴謹或者系統問題引發的故障將會導致.Net應用程序產生未捕獲異常,從而導致應用程序異常終止。本文將對三種最常見的.Net應用的未捕獲異常處理進行闡述。

      在開始本文之前,讓我們來看看.Net在什麼情況下會產生未捕獲異常。未捕獲異常從定義上說就是結構化異常處理未能捕獲的異常。通俗的講就是發生在Try Catch塊意外的異常。那麼是不是我們在Main函數中加一個Try Catch 塊就可以捕獲全部未捕獲異常了呢?答案是否定的。這裏面有兩種情況無法通過這種方法捕獲:

1. GC 產生的異常,這種異常通常因爲Finalize函數中引發未捕獲異常引起。當然這並不絕對,一些系統問題比如內存耗盡有時候也會造成GC異常。 

2. 主線程以爲的線程引發的未捕獲異常。這些異常我們往往可以在線程的主函數中用Try Catch 來捕獲,但如果系統中使用了外部的組件,或者甚至是.Net 框架自帶的一些系統組件,由這些組件的線程引發的異常,調用代碼無法通過Try Catch來捕獲。

從上面兩點來看,即使我們的代碼在每個地方都加了Try Catch ,也不能百分百杜絕未捕獲異常的發生。 

鑑於此,爲了提高系統的健壯性和可維護性,我們需要通過一種方法來截獲這些未捕獲異常,並進行適當的處理。

.Net 的設計者已經考慮到這些問題,並且爲我們提供了一個叫 UnhandledExceptionEventHandler 的事件,通過這個事件,我們可以截獲未捕獲異常,並進行處理。

這個事件的事件參數UnhandledExceptionEventArgs e, 有兩個屬性,一個是ExceptionObject,這個屬性返回爲截獲異常的對象實例。還有一個屬性是IsTerminating,這個屬性告訴我們這個異常是否會導致應用終止。這裏需要說明的是,對於.Net1.1 和 .Net2.0及以上,情況是不一樣的,.Net1.1 只有在主線程中的未捕獲異常纔會終止應用程序,而.Net2.0及以上版本則是始終終止應用程序。如果不終止應用程序,而是有CLR 將當前異常消化,系統的運行狀態很可能不可控,最後可能會發生更大的故障,所以.Net2.0以後,對於所有未捕獲異常,一律終止當前應用。這樣看來,對於.net2.0以上的應用似乎我們截獲未捕獲異常已經毫無意義,其實不然。通過截獲爲未捕獲異常,我們可以記錄下程序是再哪裏產生這種未捕獲異常的,以便程序的開發者改進程序。我們也可以在當前應用退出前爲系統做一些其他的保護工作,比如備份數據,告警提示等等。

下面我們來看看三種常見的.Net應用分別如何來截獲未捕獲異常。

 

  • 控制檯應用

  

     首先爲當前AppDomain 添加  UnhandledExceptionEventHandler

AppDomain.CurrentDomain.UnhandledException += 

             new UnhandledExceptionEventHandler(UnhandledExceptionEventHandler);

再添加事件響應函數

複製代碼
static void UnhandledExceptionEventHandler(object sender, UnhandledExceptionEventArgs e)

        {

            try

            {


                using (System.IO.FileStream fs = new System.IO.FileStream(@"c:\testme.log", 
                         System.IO.FileMode.Append, System.IO.FileAccess.Write))


                {


                    using (System.IO.StreamWriter w = new System.IO.StreamWriter(fs, 
                             System.Text.Encoding.UTF8))


                    {

                        w.WriteLine(e.ExceptionObject);

                    }

                }

            }

            catch

            {

            }

        }
複製代碼

現在我們就可以截獲未捕獲異常了

下面是完整的測試代碼:

複製代碼
public class TestRaiseException

        {

            ~TestRaiseException()

            {

                int i = 0;

                int j = 1 / i;

            }

        }



        static void UnhandledExceptionEventHandler(object sender, UnhandledExceptionEventArgs e)

        {

            try

            {


                using (System.IO.FileStream fs = new System.IO.FileStream(@"c:\testme.log",
                      System.IO.FileMode.Append, System.IO.FileAccess.Write))

                 {




                    using (System.IO.StreamWriter w = new System.IO.StreamWriter(fs, 
                         System.Text.Encoding.UTF8))

                     {



                        w.WriteLine(e.ExceptionObject);

                    }

                }

            }

            catch

            {

            }

        }



        static void Main(string[] args)

        {


            AppDomain.CurrentDomain.UnhandledException += 
                    new UnhandledExceptionEventHandler(UnhandledExceptionEventHandler);
 
 



            TestRaiseException testRaiseException = new TestRaiseException();



        }
複製代碼

程序運行後記錄下日誌如下

System.DivideByZeroException: Attempted to divide by zero.
   at TestCosole.Program.TestRaiseException.Finalize() 

 

  • WinForm

    WinForm 應用通過 Application.ThreadException 事件來截獲未捕獲異常

 詳見 園子裏面另一篇博客,這裏就不再冗訴。

    繼續看。。。。。。。。。

經常有客戶抱怨程序遇到錯誤的時候程序就退出了,甚至來不及保存窗體的輸入數據,如果是普通的搜索界面也就罷了,但如果客戶是呼叫中心,問題就嚴重了.按道理,我們的程序員應該處理所有的異常,但有事實上做到的很難,因爲出乎意料的情況太多了.

在這篇文章,我給大家介紹一下我的處理方法。

首先介紹相應的C#相關知識:

System.Windows.Forms.Application類

提供 static 方法和屬性以管理應用程序,例如啓動和停止應用程序、處理 Windows 消息的方法和獲取應用程序信息的屬性。

 

System.Windows.Forms.Application.ThreadException 事件

在發生未捕獲線程異常時發生。

 

System.Windows.Forms.Application.SetUnhandledExceptionMode()方法

指示應用程序如何響應未處理的異常。

  • SetUnhandledExceptionMode(UnhandledExceptionMode)

    指示應用程序如何響應未處理的異常。

  • SetUnhandledExceptionMode(UnhandledExceptionMode, Boolean)

    指示應用程序如何響應未處理的異常,同時可選擇應用特定於線程的行爲。

 

System.Windows.Forms.UnhandledExceptionMode枚舉

定義 Windows 窗體應用程序應在何處發送未處理的異常。

public enum UnhandledExceptionMode

{

Automatic,        //將所有異常都傳送到 ThreadException 處理程序,除非應用程序的配置文件指定了其他位置。

ThrowException,    //從不將異常傳送到 ThreadException 處理程序。忽略應用程序配置文件。

CatchException        //始終將異常傳送到 ThreadException 處理程序。忽略應用程序配置文件。

}

 

以下是我的實現示例:

複製代碼
/// <summary> 
/// 應用程序的主入口點。 
/// </summary> 
[STAThread] 
static void Main() 
{ 
     
    //應用程序的主入口點添加ThreadException的事件處理。 
    Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException); 
     
} 
 
static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) 
{ 
    //作爲示例,這裏用消息框顯示異常的信息 
    MessageBox.Show(e.Exception.Message,"異常",MessageBoxButtons.OK,MessageBoxIcon.Error); 
}
複製代碼
  • Asp.net

     ASP.NET 應用和前兩種應用有所不同,ASP.NET 一般在後臺線程或者線程池中產生未捕獲異常,纔會導致W3WP.exe終止,並在事件查看器中產生一條類似下面內容的事件:EventType clr20r3, P1 w3wp.exe, P2 6.0.3790.1830, P3 42435be1, P4 app_web_ncsnb2-n, P5 0.0.0.0, P6 440a4082, P7 5, P8 1, P9 system.nullreferenceexception, P10 NIL. 

     要截獲ASP.NET 的未捕獲異常,我們需要爲每個應用程序域安裝事件鉤子

     這個過程需要分兩步完成:

     首先創建一個實現IHttpModule接口的類 

複製代碼
using System;

using System.Data;

using System.Configuration;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;



namespace WebMonitor

{

    /**//// <summary>

    /// Summary description for UnhandledExceptionModule

    /// </summary>

    public class UnhandledExceptionModule : IHttpModule

    {

        static object _initLock = new object();

        static bool _initialized = false;



        public UnhandledExceptionModule()

        {

            //

            // TODO: Add constructor logic here

            //

        }





        void OnUnhandledException(object o, UnhandledExceptionEventArgs e)

        {

            //Do some thing you wish to do when the Unhandled Exception raised.



            try

            {


                using (System.IO.FileStream fs = new System.IO.FileStream(@"c:\testme.log", 
                        System.IO.FileMode.Append, System.IO.FileAccess.Write))

                 {




                    using (System.IO.StreamWriter w = new System.IO.StreamWriter(fs, System.
                            Text.Encoding.UTF8))

                     {



                        w.WriteLine(e.ExceptionObject);

                    }

                }

            }

            catch

            {

            }

        }

        IHttpModule Members#region IHttpModule Members



        public void Dispose()

        {

            throw new Exception("The method or operation is not implemented.");

        }



        public void Init(HttpApplication context)

        {

            // Do this one time for each AppDomain.

            lock (_initLock)

            {

                if (!_initialized)

                {

                    AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(OnUnhandledException);

                    _initialized = true;

                }

            }

        }



        #endregion

    }

}
複製代碼

第二步:

 修改web.config

在 system.web 段中加入

 <httpModules>
      <add name="UnhandledExceptionModule" type="WebMonitor.UnhandledExceptionModule" />
      
    </httpModules>

完成這兩步後,你的ASP.NET 應用就可以截獲未捕獲異常了。

 下面是測試代碼

複製代碼
using System;

using System.Data;

using System.Configuration;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;



public partial class _Default : System.Web.UI.Page 

{

    protected void Page_Load(object sender, EventArgs e)

    {



    }



    protected void TestMe(object state)

    {

        byte[] buf = new byte[2];

        buf[2] = 0;

    }



    protected void Button1_Click(object sender, EventArgs e)

    {


        System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(TestMe), 
                  null);
 
     }



}
複製代碼

按下Button1後,w3wp.exe被終止,testme.log 中記錄下了異常信息如下:

System.IndexOutOfRangeException: Index was outside the bounds of the array.

   at _Default.TestMe(Object state) in c:"ApolloWorkFolder"test"laboratory

"TestWebSite"Default.aspx.cs:line 21

   at System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state)
   at System.Threading.ExecutionContext.runTryCode(Object userData)

   at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(

TryCode code, CleanupCode backoutCode, Object userData)

   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext,

ContextCallback callback, Object state)

   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext,

ContextCallback callback, Object state)

   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal(

_ThreadPoolWaitCallback tpWaitCallBack)

   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state)

 

發佈了138 篇原創文章 · 獲贊 32 · 訪問量 107萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章