前幾天做CA簽名這個需求時發現一個很詭異的事情,CA簽名調用的接口是由另外一個開發部門的同事(比較難溝通的那種人)封裝并提供到我們這邊的。我們這邊只需要把數據準備好,然后調他封裝的接口即可完成簽名操作。但在測試過程中,發現他提供的接口在某些邊界條件時,會報錯。通過反編譯調試后,把報錯的堆棧及要如何修改都發給了那個同事,但是他沒鳥我,項目經理他不懂技術,也不想管這個事情(所以以后跳槽一定要跳到一個好一點的團隊)。我該做的都已經做了,沒辦法,畢竟是我負責的功能需求,到時候報錯了也是第一時間找到我。我這邊就try catch捕獲一下異常唄,神奇的事情出現了,沒捕獲到,而是被Application.ThreadException事件注冊的方法給捕獲到了(這里捕獲這個詞不算很恰當,即觸發Application.ThreadException事件對應的方法)。我們都知道,UI線程中未捕獲的異常,如果在程序的Main方法入口注冊了Application.ThreadException事件對應的方法,UI線程發生異常如果未捕獲并處理該異常就會觸發Application.ThreadException事件對應的方法。這就說明我try catch不到他那個接口的異常信息。
我這邊處理的業務邏輯代碼大概可以描述為:
通過反編譯看了一下"調用封裝CA簽名接口的代碼塊"對應的代碼,它的大概處理流程是這樣的:先通過Spring.Net接口調用CA簽名的業務邏輯,記為業務邏輯A,業務邏輯A的實現流程如下:通過反射,拿到對應的CA簽名的實現類(因為我們這邊的代碼需要兼容多個CA簽名的廠商),我們這邊對接的是網政通的CA,我這邊就只介紹一下它的大概流程:先獲取提供接口的CA用戶的用戶信息,記為步驟1;如果有用戶信息,則需要再次調用獲取用戶token信息接口,記為步驟2;獲取token用戶信息成功后,再調用獲取CA用戶二維碼信息的接口,獲取到簽章并以二維碼的形式顯示出來讓用戶進行掃碼操作,記為步驟3。如果前面的步驟1不成功,后面的步驟2,3都不用繼續操作了,直接返回CA簽名失敗,走普通簽名邏輯。同事的接口報錯就發生在步驟1中,沒有CA用戶信息時,某些代碼邏輯寫得不夠嚴謹,就報錯了。
至于我這邊為何try catch步驟1中發生的異常信息,我做了如下的猜測并進行了驗證
1? ?是不是spring.net的框架把它給處理了,結合前面使用過spring.net的經驗,排除了這種可能性
2? ?是不是被反射的方法里面報錯,調用方就抓不到異常,不太確定,那就用代碼驗證一下,后面驗證過了,反射的雖然拿不到具體的報錯堆棧信息,但還是能通過try catch捕獲到異常信息的。
3? 是不是他的代碼里面有我不知道的異常處理方式,但是看了好久,也沒看出哪里有特別的地方
4? 是不是在不同的AppDomain的異常,就捕獲不到,后面也嘗試過了,也是能捕獲的
前面的猜測無果后,就一路在網上查詢C#中try catch不到異常的情況:
網上說的情況(未驗證):有說調用非托管的代碼就捕獲不到異常
其它靠譜一點的捕獲不到異常的情況:
文章鏈接1 (未做驗證):Exception not caught using catch block
StackOverflowException:堆棧溢出異常
ThreadAbortedException:線程停止異常
OutOfMemoryException:堆棧溢出異常
ExcutionEngineException:執行引擎異常
BadImageFormatException:錯誤圖片類型異常
文章鏈接2 (未做驗證):The Uncatchable Exception
情況1:出現死遞歸導致內存異常的異常:
情況2:處理的異常中人工調用了Environment.FailFast,捕獲不到異常,程序直接退出
不過都不是我要的解決方案,當看到Environment.FailFast時,突然靈光一閃,是不是winform框架給捕獲了,然后再手工調用某個方法,會觸發Application.ThreadException事件對應的方法。有了思路后,再來反調試代碼,發現同事重寫了winfrom窗體的OnLoad方法,在重新的OnLoad方法中完成步驟1操作,而在反編譯調試中,看到winfrom窗體調用OnLoad方法的調用方捕獲了異常,并調用Application.OnException觸發Application.ThreadException事件對應的方法,如下圖:
下面我們就一起驗證一下這種情況:
測試環境:
.net framework 4.0
visual studio 2017
具體步驟如下:
1? ?新增名為TestMain的winfrom項目
2? ?編輯默認的Program類如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;namespace TestMain
{static class Program{/// <summary>/// 應用程序的主入口點。/// </summary>[STAThread]static void Main(){Application.EnableVisualStyles();Application.SetCompatibleTextRenderingDefault(false);Application.ThreadException += Application_ThreadException;Application.Run(new Form1());}private static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e){MessageBox.Show("Main方法中的Application_Thread輸出,詳細錯誤信息如下:" + e.Exception.Message + e.Exception.StackTrace);}}
}
這里我注冊了Application.ThreadException事件回調的方法Application_ThreadException,如果UI線程中有沒有處理的異常,就會觸發這個方法。
3? 新增winform窗體,名為QRCodeFrm,對應的UI界面設計如下:
對應的后臺代碼如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;namespace TestMain
{public partial class QRCodeFrm : Form{public QRCodeFrm(){InitializeComponent();}protected override void OnLoad(EventArgs e){bool flag = true;if (flag){int a = 1;int b = 0;//這里會拋出異常int c = a / b;}}}
}
在這里,我們重寫了OnLoad方法,然后再進行a/b的除以0操作,這里運行時會報異常
4? ?在默認的Form1窗體中拖入一個按鈕,UI界面如下圖:
button1按鈕對應的邏輯如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using TestApi;namespace TestMain
{public partial class Form1 : Form{public Form1(){InitializeComponent();}private void button1_Click(object sender, EventArgs e){try{QRCodeFrm frm = new QRCodeFrm();frm.ShowDialog();}catch (Exception ex){MessageBox.Show("捕獲到異常,異常信息如下:"+ex.Message+ex.StackTrace);}}}
}
在button1_Click我們進行捕獲異常
5? 生成項目并運行,結果如下:
可以看到Application.ThreadException事件回調的方法Application_ThreadException已經被調用,接著后彈出QRCodeFrm對應的窗體,如下圖:
可以看到,已經按照猜想那樣進行了輸出顯示。
回到最初的那個問題,我們要怎么處理才能捕獲到同事接口的那個異常信息呢,有個不是很靠譜的方法是,我們在合適的地方重新注冊Application.ThreadException事件方法,我們都知道,通過+=的方式注冊的Application.ThreadException事件方法,前面已經注冊過的事件方法就會被覆蓋。修改前面演示的例子中的Form1,并編輯如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using TestApi;namespace TestMain
{public partial class Form1 : Form{bool isCatch = false;string errorMessage = string.Empty;public Form1(){InitializeComponent();Application.ThreadException += New_Application_ThreadException;}private void New_Application_ThreadException(object sender, ThreadExceptionEventArgs e){errorMessage = e.Exception.Message + e.Exception.StackTrace;isCatch = true;}private void button1_Click(object sender, EventArgs e){try{QRCodeFrm frm = new QRCodeFrm();frm.ShowDialog();}catch (Exception ex){MessageBox.Show("捕獲到異常,異常信息如下:"+ex.Message+ex.StackTrace);}if (isCatch){MessageBox.Show("被捕獲的異常:"+errorMessage);}}}}
運行結果如下:
接著會彈出粗我提示框如下:
可以看到,Main方法中注冊的Application.ThreadException事件方法的已經被新注冊的方法給覆蓋了
注意:這種解決方案風險比較大,我這邊新增了一個參數進行控制是否進行Application.ThreadException事件方法的重新注冊,等同事修改了代碼,我這邊就會把參數進行關閉,這算是留了一手吧
本文的內容到此結束,內容僅代表個人觀點,如有寫得不對的地方,望指正。