![](/img/trans.png)
[英]How to catch Exception thrown in a method invoked by using MethodInfo.Invoke?
[英]Any workaround to get outer exceptions from an exception thrown inside an Invoked method in WinForms?
在 Windows 窗體中,當對Control.Invoke
或Control.BeginInvoke
的調用引發異常並且它未被任何 catch 塊處理時,它可以由控件或應用程序級處理程序通過Windows.Forms.Application.ThreadException
處理事件。 它的處理程序接受一個帶有屬性Exception
的EventArgs
,這是拋出的異常。 但是,在幕后,Windows.Forms.Control(不受歡迎)根據這個答案剝離了除了最內部的例外之外的所有內容。
也找到了這篇博客文章,但它沒有建議任何解決方法。
(這目前導致我在我的錯誤日志中得到一個微不足道的堆棧跟蹤——一個告訴我最里面的細節的堆棧跟蹤,但是沒有提到調用代碼,也沒有辦法找出這種錯誤的代碼位置。 )
有解決方法嗎? 有沒有辦法在我的應用程序事件處理程序中獲取所有外部異常(正在記錄意外異常以進行故障排除)?
我發現的唯一建議似乎是在調用的代碼中捕獲異常並將其一些信息填充到Exception.Data
中,也許是在一個新的異常中——但如果我知道導致異常的外部代碼,我可以修復錯誤而不是記錄它。 相反,如果不圍繞每個候選代碼塊包裝一個 try-catch,我怎么能在全局范圍內做到這一點?
這無疑是一種 hack,但它是我能想出的最好的解決方案,它支持 WinForms 中的全局異常處理和所有異常,即使有內部異常也是如此。
在Program.cs
中:
internal static class Program
{
[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();
AppDomain.CurrentDomain.FirstChanceException += CurrentDomain_FirstChanceException;
Application.ThreadException += Application_ThreadException;
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException, true);
Application.Run(new MyMainForm());
}
private static void CurrentDomain_FirstChanceException(object sender, FirstChanceExceptionEventArgs e)
{
_outermostExceptionCache.AddException(e.Exception);
}
private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{
Exception exception = null;
if (e?.Exception != null)
exception = _outermostExceptionCache.GetOutermostException(e.Exception);
// Handle exception
}
private static OutermostExceptionCache _outermostExceptionCache = new();
}
為此,您需要OutermostExceptionCache
類:
public class OutermostExceptionCache
{
public void AddException(Exception ex)
{
if ((ex != null) && (ex is not TargetInvocationException))
{
Exception innermostException = GetInnermostException(ex);
lock (_syncRoot)
{
RemoveOldEntries();
_cache[innermostException] = new CacheEntry(ex);
}
}
}
public Exception GetOutermostException(Exception ex)
{
Exception innermostException = GetInnermostException(ex);
Exception outermostException = null;
lock (_syncRoot)
{
if (_cache.TryGetValue(innermostException, out CacheEntry entry))
{
outermostException = entry.Exception;
_cache.Remove(innermostException);
}
else
{
outermostException = ex;
}
}
return outermostException;
}
private void RemoveOldEntries()
{
DateTime now = DateTime.Now;
foreach (KeyValuePair<Exception, CacheEntry> pair in _cache)
{
TimeSpan timeSinceAdded = now - pair.Value.AddedTime;
if (timeSinceAdded.TotalMinutes > 3)
_cache.Remove(pair.Key);
}
}
private Exception GetInnermostException(Exception ex)
{
return ex.GetBaseException() ?? ex;
}
private readonly object _syncRoot = new();
private readonly Dictionary<Exception, CacheEntry> _cache = new();
private class CacheEntry
{
public CacheEntry(Exception ex)
{
Exception = ex;
AddedTime = DateTime.Now;
}
public Exception Exception { get; }
public DateTime AddedTime { get; }
}
}
它的工作方式是在運行時甚至將異常冒泡到最近的 catch 塊之前觀察每個異常,因為它被拋出。 每次拋出異常時,都會將其添加到緩存中,並由最內層(即基本)異常進行索引。 因此,當一個異常被捕獲並拋出一個新異常時,以原來的異常作為其內部異常,緩存將使用該外部異常進行更新。 然后,當Application.ThreadException
事件處理程序提供了未包裝的最內層異常時,處理程序可以從緩存中查找最外層的異常。
注意:由於即使是本地捕獲的異常也會被添加到緩存中(因此永遠不會通過調用GetOutermostException
來刪除),它會為每個異常添加時間戳並自動丟棄任何超過 3 分鍾的異常。 這是一個任意超時,可以根據需要進行調整。 如果將超時設置得太短,可能會導致調試出現問題,因為如果在調試器中暫停進程太久(在拋出異常之后但在處理之前),它可能會導致異常處理恢復為僅處理最里面的異常)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.