Bắt ngoại lệ trên toàn cầu trong một ứng dụng WPF?


242

Chúng tôi đang có một ứng dụng WPF trong đó các phần của nó có thể đưa ra ngoại lệ khi chạy. Tôi muốn bắt toàn cầu bất kỳ ngoại lệ nào chưa được xử lý và ghi lại chúng, nhưng nếu không thì tiếp tục thực hiện chương trình như thể không có gì xảy ra (giống như VB On Error Resume Next).

Điều này có thể có trong C #? Và nếu vậy, chính xác thì tôi cần đặt mã xử lý ngoại lệ ở đâu?

Hiện nay tôi không thể nhìn thấy bất kỳ điểm duy nhất mà tôi có thể quấn một try/ catchxung quanh và trong đó sẽ bắt tất cả các trường hợp ngoại lệ có thể xảy ra. Và thậm chí sau đó tôi sẽ để lại bất cứ thứ gì đã bị xử tử vì bị bắt. Hay tôi đang suy nghĩ sai hướng khủng khiếp ở đây?

ETA: Bởi vì nhiều người dưới đây đã chỉ ra: Ứng dụng này không dành cho việc kiểm soát các nhà máy điện hạt nhân. Nếu nó gặp sự cố thì đó không phải là vấn đề lớn nhưng các trường hợp ngoại lệ ngẫu nhiên chủ yếu liên quan đến UI là một mối phiền toái trong bối cảnh sử dụng nó. Có (và có lẽ vẫn còn) một vài trong số đó và vì nó sử dụng kiến ​​trúc plugin và có thể được mở rộng bởi những người khác (cũng là sinh viên trong trường hợp đó; vì vậy không có nhà phát triển kinh nghiệm nào có thể viết mã hoàn toàn không có lỗi).

Đối với các trường hợp ngoại lệ bị bắt: Tôi đăng nhập chúng vào một tệp nhật ký, bao gồm cả dấu vết ngăn xếp hoàn chỉnh. Đó là toàn bộ điểm của bài tập đó. Chỉ để chống lại những người đã đưa sự tương tự của tôi đến Oern của VB theo đúng nghĩa đen.

Tôi biết rằng việc mù quáng bỏ qua các lớp lỗi nhất định là nguy hiểm và có thể làm hỏng ứng dụng của tôi. Như đã nói, chương trình này không phải là nhiệm vụ quan trọng đối với bất kỳ ai. Không ai trong tâm trí của họ sẽ đặt cược sự sống còn của nền văn minh nhân loại vào nó. Nó chỉ đơn giản là một công cụ nhỏ để thử nghiệm phương pháp thiết kế nhất định. kỹ thuật phần mềm.

Đối với việc sử dụng ngay lập tức của ứng dụng, không có nhiều điều có thể xảy ra với một ngoại lệ:

  • Không có xử lý ngoại lệ - hộp thoại lỗi và thoát ứng dụng. Thử nghiệm phải được lặp lại, mặc dù có khả năng với một đối tượng khác. Không có lỗi đã được đăng nhập, đó là điều không may.
  • Xử lý ngoại lệ chung - lỗi lành tính bị mắc kẹt, không có tác hại. Đây phải là trường hợp phổ biến được đánh giá từ tất cả các lỗi chúng ta đã thấy trong quá trình phát triển. Bỏ qua loại lỗi này sẽ không có hậu quả ngay lập tức; cấu trúc dữ liệu cốt lõi được kiểm tra đủ tốt để họ có thể dễ dàng sống sót qua việc này.
  • Xử lý ngoại lệ chung - lỗi nghiêm trọng bị mắc kẹt, có thể gặp sự cố tại một điểm sau đó. Điều này có thể hiếm khi xảy ra. Chúng tôi chưa bao giờ nhìn thấy nó cho đến nay. Dù sao thì lỗi cũng được ghi lại và sự cố có thể xảy ra. Vì vậy, đây là khái niệm tương tự như trường hợp đầu tiên. Ngoại trừ việc chúng ta có một dấu vết ngăn xếp. Và trong phần lớn các trường hợp, người dùng thậm chí sẽ không nhận thấy.

Đối với dữ liệu thử nghiệm do chương trình tạo ra: Một lỗi nghiêm trọng nhất sẽ khiến không có dữ liệu nào được ghi lại. Những thay đổi tinh tế làm thay đổi kết quả của thí nghiệm từng chút một là rất khó xảy ra. Và ngay cả trong trường hợp đó, nếu kết quả có vẻ đáng ngờ thì lỗi đã được ghi lại; người ta vẫn có thể vứt bỏ điểm dữ liệu đó nếu nó hoàn toàn là ngoại lệ.

Tóm lại: Có, tôi cho rằng bản thân mình vẫn còn ít nhất một phần lành mạnh và tôi không xem xét một thói quen xử lý ngoại lệ toàn cầu khiến chương trình chạy hoàn toàn xấu xa. Như đã nói hai lần trước, một quyết định như vậy có thể có hiệu lực, tùy thuộc vào ứng dụng. Trong trường hợp này, nó đã được đánh giá là một quyết định hợp lệ và không hoàn toàn nhảm nhí. Đối với bất kỳ ứng dụng nào khác mà quyết định có thể trông khác nhau. Nhưng xin đừng buộc tội tôi hoặc những người khác đã làm việc trong dự án đó có khả năng làm nổ tung thế giới chỉ vì chúng tôi bỏ qua lỗi.

Lưu ý bên lề: Có chính xác một người dùng cho ứng dụng đó. Nó không phải là một cái gì đó giống như Windows hoặc Office được sử dụng bởi hàng triệu người trong đó chi phí có ngoại lệ bong bóng cho người dùng hoàn toàn sẽ khác nhau ở nơi đầu tiên.


Điều này không thực sự suy nghĩ - có, bạn có thể muốn ứng dụng thoát. NHƯNG, sẽ không tốt khi lần đầu tiên đăng nhập ngoại lệ với StackTrace của nó? Nếu tất cả những gì bạn nhận được từ người dùng là "Ứng dụng của bạn bị sập khi tôi nhấn nút này", bạn có thể không bao giờ có thể giải quyết vấn đề vì bạn sẽ không có đủ thông tin. Nhưng nếu bạn lần đầu tiên đăng nhập ngoại lệ trước khi hủy bỏ ứng dụng một cách dễ chịu hơn, bạn sẽ có nhiều thông tin hơn đáng kể.
Nga

Tôi đã xây dựng điểm đó một chút trong câu hỏi bây giờ. Tôi biết những rủi ro liên quan và đối với ứng dụng cụ thể đó, nó được coi là chấp nhận được. Và việc hủy bỏ ứng dụng cho một cái gì đó đơn giản như một chỉ số nằm ngoài giới hạn trong khi UI cố gắng thực hiện một số hoạt hình đẹp quá mức cần thiết và không cần thiết. Có, tôi không biết nguyên nhân chính xác nhưng chúng tôi có dữ liệu để sao lưu xác nhận rằng phần lớn các trường hợp lỗi là lành tính. Những vấn đề nghiêm trọng mà chúng tôi đang che giấu có thể khiến ứng dụng bị sập nhưng đó là điều sẽ xảy ra nếu không xử lý ngoại lệ toàn cầu.
Joey

Một lưu ý khác: nếu bạn ngăn chặn bất kỳ sự cố nào với phương pháp này, người dùng rất có thể sẽ thích nó.
lahjaton_j

Xem mẫu Xử lý ngoại lệ chưa xử lý của Windows trong mẫu WPF (Bộ sưu tập trình xử lý đầy đủ nhất) trong C # cho Visual Studio 2010 . Nó có 5 ví dụ bao gồm AppDomain.CienDomain.FirstChanceException, Application.DispatcherUnhandledException và AppDomain.CienDomain.UnhandledException.
user34660

Tôi muốn thêm rằng dòng mã giống như VB On Error Resume Nextlà không thể có trong C #. Sau khi Exception(C # không có "lỗi"), bạn không thể tiếp tục với câu lệnh tiếp theo: thực thi sẽ tiếp tục trong một catchkhối - hoặc trong một trong các trình xử lý sự kiện được mô tả trong các câu trả lời bên dưới.
mike

Câu trả lời:


191

Sử dụng Application.DispatcherUnhandledException Event. Xem câu hỏi này để biết tóm tắt (xem câu trả lời của Drew Noakes ).

Xin lưu ý rằng sẽ vẫn có những trường hợp ngoại lệ ngăn chặn việc khôi phục thành công ứng dụng của bạn, như sau khi tràn ngăn xếp, hết bộ nhớ hoặc mất kết nối mạng trong khi bạn đang cố lưu vào cơ sở dữ liệu.


Cảm ơn. Và vâng, tôi biết rằng có những trường hợp ngoại lệ mà tôi không thể phục hồi, nhưng phần lớn những trường hợp có thể xảy ra là lành tính trong trường hợp này.
Joey

14
Nếu ngoại lệ của bạn xảy ra trên một luồng nền (giả sử sử dụng ThreadPool.QueueUserWorkItem), thì điều này sẽ không hoạt động.
Szymon Rozga

@siz: điểm tốt, nhưng những điểm đó có thể được xử lý với một khối thử bình thường xung quanh trong văn bản, không?
David Schmitt

1
@PitiOngmongkolkul: Trình xử lý được gọi là sự kiện từ vòng lặp chính của bạn. Khi xử lý sự kiện trở lại, ứng dụng của bạn tiếp tục bình thường.
David Schmitt

4
Có vẻ như chúng ta cần đặt e.Handled = true, trong đó e là một DispatcherUnhandledExceptionEventArss, để bỏ qua trình xử lý mặc định thoát khỏi các chương trình. msdn.microsoft.com/en-us/l
Library / từ

55

Mã ví dụ sử dụng NLog sẽ bắt ngoại lệ được ném từ tất cả các luồng trong AppDomain , từ luồng xử lý giao diện người dùng và từ các hàm async :

Ứng dụng.xaml.cs:

public partial class App : Application
{
    private static Logger _logger = LogManager.GetCurrentClassLogger();

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        SetupExceptionHandling();
    }

    private void SetupExceptionHandling()
    {
        AppDomain.CurrentDomain.UnhandledException += (s, e) =>
            LogUnhandledException((Exception)e.ExceptionObject, "AppDomain.CurrentDomain.UnhandledException");

        DispatcherUnhandledException += (s, e) =>
        {
            LogUnhandledException(e.Exception, "Application.Current.DispatcherUnhandledException");
            e.Handled = true;
        };

        TaskScheduler.UnobservedTaskException += (s, e) =>
        {
            LogUnhandledException(e.Exception, "TaskScheduler.UnobservedTaskException");
            e.SetObserved();
        };
    }

    private void LogUnhandledException(Exception exception, string source)
    {
        string message = $"Unhandled exception ({source})";
        try
        {
            System.Reflection.AssemblyName assemblyName = System.Reflection.Assembly.GetExecutingAssembly().GetName();
            message = string.Format("Unhandled exception in {0} v{1}", assemblyName.Name, assemblyName.Version);
        }
        catch (Exception ex)
        {
            _logger.Error(ex, "Exception in LogUnhandledException");
        }
        finally
        {
            _logger.Error(exception, message);
        }
    }

10
Đây là câu trả lời đầy đủ nhất ở đây! Ngoại lệ lịch trình Taks được bao gồm. Tốt nhất cho tôi, mã sạch và đơn giản.
Nikola Jovic

3
Gần đây tôi có một lỗi tham gia App.config tại một khách hàng và Ứng dụng của cô ấy thậm chí không bắt đầu vì NLog đang cố đọc từ App.config và đã ném một ngoại lệ. Vì ngoại lệ đó nằm trong trình khởi tạo logger tĩnh , nên nó không bị bắt bởi UnhandledExceptiontrình xử lý. Tôi đã phải xem Windows Event Log Viewer để tìm hiểu chuyện gì đang xảy ra ...
heltonbiker

Tôi khuyên bạn nên để thiết lập e.Handled = true;tại UnhandledExceptionđó ứng dụng sẽ không sụp đổ trên UI Exceptions
Apfelkuacha

30

Sự kiện AppDomain.UnhandledException

Sự kiện này cung cấp thông báo về các trường hợp ngoại lệ chưa được phát hiện. Nó cho phép ứng dụng ghi thông tin về ngoại lệ trước khi trình xử lý mặc định của hệ thống báo cáo ngoại lệ cho người dùng và chấm dứt ứng dụng.

   public App()
   {
      AppDomain currentDomain = AppDomain.CurrentDomain;
      currentDomain.UnhandledException += new UnhandledExceptionEventHandler(MyHandler);    
   }

   static void MyHandler(object sender, UnhandledExceptionEventArgs args) 
   {
      Exception e = (Exception) args.ExceptionObject;
      Console.WriteLine("MyHandler caught : " + e.Message);
      Console.WriteLine("Runtime terminating: {0}", args.IsTerminating);
   }

Nếu sự kiện UnhandledException được xử lý trong miền ứng dụng mặc định, thì nó được nêu ra ở đó cho bất kỳ ngoại lệ nào chưa được xử lý trong bất kỳ luồng nào, bất kể miền nào ứng dụng bắt đầu trong miền. Nếu luồng bắt đầu trong miền ứng dụng có xử lý sự kiện cho UnhandledException, sự kiện này được nêu ra trong lĩnh vực ứng dụng đó. Nếu miền ứng dụng đó không phải là miền ứng dụng mặc định và cũng có một trình xử lý sự kiện trong miền ứng dụng mặc định, thì sự kiện được nêu ra trong cả hai miền ứng dụng.

Ví dụ: giả sử một luồng bắt đầu trong miền ứng dụng "AD1", gọi một phương thức trong miền ứng dụng "AD2" và từ đó gọi một phương thức trong miền ứng dụng "AD3", trong đó nó ném một ngoại lệ. Miền ứng dụng đầu tiên trong đó sự kiện UnhandledException có thể được nêu ra là "AD1". Nếu miền ứng dụng đó không phải là miền ứng dụng mặc định, sự kiện cũng có thể được nêu ra trong miền ứng dụng mặc định.


sao chép từ URL mà bạn đã trỏ đến (chỉ nói về các ứng dụng bảng điều khiển và ứng dụng WinForms mà tôi nghĩ): "Bắt đầu với .NET Framework 4, sự kiện này không được nêu ra cho các trường hợp ngoại lệ làm hỏng trạng thái của quy trình, chẳng hạn như tràn ngăn xếp hoặc vi phạm quyền truy cập, trừ khi trình xử lý sự kiện quan trọng về bảo mật và có thuộc tính HandleProcessCorruptedStateExceptionsAttribution. "
George Birbilis

1
@GeorgeBirbilis: Bạn có thể đăng ký sự kiện UnhandledException trong Trình xây dựng ứng dụng.
CharithJ

18

Ngoài những gì người khác đề cập ở đây, lưu ý rằng việc kết hợp Application.DispatcherUnhandledException(và similars của nó ) với

<configuration>
  <runtime>  
    <legacyUnhandledExceptionPolicy enabled="1" />
  </runtime>
</configuration>

trong app.configsẽ ngăn chặn ngoại lệ chủ đề phụ của bạn tắt ứng dụng.


2

Dưới đây là ví dụ đầy đủ bằng cách sử dụng NLog

using NLog;
using System;
using System.Windows;

namespace MyApp
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        public App()
        {
            var currentDomain = AppDomain.CurrentDomain;
            currentDomain.UnhandledException += CurrentDomain_UnhandledException;
        }

        private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            var ex = (Exception)e.ExceptionObject;
            logger.Error("UnhandledException caught : " + ex.Message);
            logger.Error("UnhandledException StackTrace : " + ex.StackTrace);
            logger.Fatal("Runtime terminating: {0}", e.IsTerminating);
        }        
    }


}

-4

Giống như "VB's On Error Resume Next?" Nghe có vẻ đáng sợ. Đề nghị đầu tiên là không làm điều đó. Đề nghị thứ hai là đừng làm điều đó và đừng nghĩ về nó. Bạn cần cách ly lỗi của bạn tốt hơn. Về cách tiếp cận vấn đề này, nó phụ thuộc vào cách mã của bạn được cấu trúc. Nếu bạn đang sử dụng một mô hình như MVC hoặc tương tự thì điều này không quá khó khăn và chắc chắn sẽ không yêu cầu một người nuốt ngoại lệ toàn cầu. Thứ hai, tìm kiếm một thư viện ghi nhật ký tốt như log4net hoặc sử dụng theo dõi. Chúng tôi cần biết thêm chi tiết như loại ngoại lệ nào bạn đang nói đến và phần nào trong ứng dụng của bạn có thể dẫn đến ngoại lệ bị ném.


2
Vấn đề là tôi muốn giữ cho ứng dụng chạy ngay cả sau khi ngoại lệ chưa được phát hiện. Tôi chỉ muốn đăng nhập chúng (chúng tôi có khung đăng nhập tùy chỉnh cho việc này, dựa trên nhu cầu của chúng tôi) và không cần hủy bỏ toàn bộ chương trình chỉ vì một số plugin đã làm điều gì đó kỳ lạ trong mã tương tác người dùng của nó. Từ những gì tôi thấy cho đến nay, việc cách ly các nguyên nhân một cách sạch sẽ vì các phần khác nhau không thực sự biết nhau là điều không quan trọng.
Joey

9
Tôi hiểu nhưng bạn phải rất thận trọng khi tiếp tục sau một ngoại lệ mà bạn không kiểm tra, có khả năng tham nhũng dữ liệu và vv để xem xét.
BobbyShaftoe

3
Tôi đồng ý với BobbyShaftoe. Đây không phải là cách chính xác để đi. Hãy để ứng dụng sụp đổ. Nếu lỗi là do một số plugin, sau đó sửa chữa hoặc nhờ ai đó sửa chữa plugin. Để nó chạy là TUYỆT VỜI nguy hiểm. Bạn sẽ nhận được các tác dụng phụ kỳ lạ và sẽ đạt đến điểm mà bạn sẽ không thể tìm thấy lời giải thích hợp lý cho các lỗi xảy ra trong ứng dụng của mình.

1
Tôi đã xây dựng điểm đó một chút trong câu hỏi bây giờ. Tôi biết những rủi ro liên quan và đối với ứng dụng cụ thể đó, nó được coi là chấp nhận được. Và việc hủy bỏ ứng dụng cho một cái gì đó đơn giản như một chỉ số nằm ngoài giới hạn trong khi UI cố gắng thực hiện một số hoạt hình đẹp là quá mức cần thiết và không cần thiết. Có, tôi không biết nguyên nhân chính xác nhưng chúng tôi có dữ liệu để sao lưu xác nhận rằng phần lớn các trường hợp lỗi là lành tính. Những vấn đề nghiêm trọng mà chúng tôi đang che giấu có thể khiến ứng dụng bị sập nhưng đó là điều sẽ xảy ra nếu không xử lý ngoại lệ toàn cầu.
Joey

Đây phải là một bình luận, không phải là một câu trả lời. Trả lời "làm thế nào để tôi làm điều này" với "có lẽ bạn không nên" không phải là một câu trả lời, đó là một nhận xét.
Josh Noe
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.