Khi chúng tôi thảo luận về Trình xử lý sự kiện không đồng bộ, điều đầu tiên mà nhiều người trong chúng tôi nghĩ đến là đó là ngoại lệ duy nhất mà chúng tôi dường như cho phép
Khi tôi viết về điều này trước đây, tôi đã rất phấn khích vì mình đang khám phá một giải pháp liên quan đến việc thực sự cho phép tồn tại khoảng trống không đồng bộ (mà không muốn nhổ phần tóc còn lại của mình ra).
Đối với tôi, điều này liên quan nhiều hơn đến một số thủ thuật thông minh mà chúng tôi có thể sử dụng để khắc phục Trình xử lý sự kiện không đồng bộ hơn là cung cấp giải pháp để tránh hoàn toàn sự cố.
Mặc dù vậy, như đã nói, có rất nhiều điểm thu hút đối với bài viết, điều mà tôi rất biết ơn và một số người đã bày tỏ ý kiến rằng họ muốn giải quyết EventHandlers không đồng bộ theo một cách khác.
Tôi nghĩ rằng đây là một điểm tuyệt vời, vì vậy tôi muốn đưa ra một phương pháp thay thế không sửa lỗi async void, nhưng nó cho phép bạn bỏ qua hoàn toàn (xem tôi đã làm gì ở đó?) trong khi giải quyết một số thách thức với Trình xử lý sự kiện không đồng bộ.
Trong bài viết này, tôi sẽ trình bày một giải pháp khác mà bạn có thể thử bằng mã của riêng mình. Chúng tôi sẽ giải quyết những ưu và nhược điểm theo quan điểm của tôi về cách sử dụng nó để bạn có thể quyết định xem nó có phù hợp với trường hợp sử dụng của bạn hay không.
Bạn cũng có thể tìm thấy một số mã có thể tương tác trên .NET fiddle phải không
Vấn đề mà chúng tôi gặp phải với async EventHandlers là chữ ký cho các sự kiện mà chúng tôi có thể đăng ký trong C# theo mặc định trông giống như sau:
void TheObject_TheEvent(object sender, EventArgs e);
Và, bạn sẽ nhận thấy rằng bằng cách bỏ trống phía trước chữ ký này, chúng tôi buộc phải sử dụng void trong trình xử lý của riêng mình để đăng ký sự kiện.
Điều này có nghĩa là nếu bạn muốn trình xử lý của mình chạy mã async/await, thì bạn sẽ cần phải đợi bên trong phương thức void của mình… Điều này giới thiệu mẫu void async lớn đáng sợ mà chúng ta được yêu cầu phải tránh giống như bệnh dịch hạch.
Và tại sao? Bởi vì async void phá vỡ khả năng các ngoại lệ nổi lên đúng cách và kết quả là có thể gây ra rất nhiều vấn đề đau đầu.
Theo ý kiến của tôi, càng đơn giản càng tốt… vì vậy nếu bạn đã đọc bài viết trước của tôi về async void và mục tiêu của bạn thực sự chỉ là xử lý EventHandlers, điều này sẽ hữu ích.
Dựa trên các điều kiện đã nêu trước đó, việc xử lý ngoại lệ bị phá vỡ trên ranh giới của khoảng trống không đồng bộ. Nếu bạn có một ngoại lệ cần nổi lên khi vượt qua ranh giới này, thì bạn sẽ có một khoảng thời gian vui vẻ.
Và nói một cách thú vị, ý tôi là nếu bạn thích gỡ lỗi tại sao mọi thứ không hoạt động và bạn không có dấu hiệu rõ ràng về những gì đang bị hỏng, thì bạn sẽ thực sự có một khoảng thời gian tuyệt vời.
Vì vậy, cách dễ nhất để khắc phục điều này là gì?
Trước tiên, hãy ngăn chặn các trường hợp ngoại lệ có thể vượt qua ranh giới này bằng cách sử dụng một công cụ đơn giản mà chúng tôi có quyền truy cập: try/catch.
objectThatRaisesEvent.TheEvent += async (s, e) => { // if the try catch surrounds EVERYTHING in the handler, no exception can bubble up try { await SomeTaskYouWantToAwait(); } catch (Exception ex) { // TODO: put your exception handling stuff here } // no exception can escape here if the try/catch surrounds the entire handler body }
Như đã lưu ý trong đoạn mã trên, nếu bạn đặt một khối try/catch xung quanh TOÀN BỘ phần thân của trình xử lý sự kiện, thì bạn có thể ngăn chặn bất kỳ ngoại lệ nào nổi lên trên ranh giới khoảng trống không đồng bộ đó. Nhìn bề ngoài, nó khá đơn giản và không yêu cầu bất kỳ thứ gì lạ mắt để thực hiện điều này.
Ưu điểm:
Nhược điểm:
Như đã nói, giải pháp này thực sự đơn giản, nhưng tôi nghĩ chúng ta có thể làm tốt hơn một chút.
Một cải tiến mà tôi nghĩ rằng chúng ta có thể thực hiện đối với giải pháp được đề xuất ban đầu là chúng ta có thể làm rõ hơn một chút rằng chúng ta có một EventHandler không đồng bộ sẽ an toàn trước các ngoại lệ nổi lên.
Cách tiếp cận này cũng sẽ ngăn mã trôi theo thời gian khiến mã có vấn đề chạy bên ngoài trình xử lý sự kiện. Tuy nhiên, nó sẽ không giải quyết được thực tế là bạn cần nhớ thêm phần này vào theo cách thủ công!
Hãy kiểm tra mã:
static class EventHandlers { public static EventHandler<TArgs> TryAsync<TArgs>( Func<object, TArgs, Task> callback, Action<Exception> errorHandler) where TArgs : EventArgs => TryAsync<TArgs>( callback, ex => { errorHandler.Invoke(ex); return Task.CompletedTask; }); public static EventHandler<TArgs> TryAsync<TArgs>( Func<object, TArgs, Task> callback, Func<Exception, Task> errorHandler) where TArgs : EventArgs { return new EventHandler<TArgs>(async (object s, TArgs e) => { try { await callback.Invoke(s, e); } catch (Exception ex) { await errorHandler.Invoke(ex); } }); } }
Đoạn mã trên hoàn toàn sử dụng cùng một cách tiếp cận để ngăn chặn các ngoại lệ vượt qua ranh giới khoảng trống không đồng bộ. Chúng tôi chỉ đơn giản là cố gắng nắm bắt xung quanh phần thân của trình xử lý sự kiện, nhưng bây giờ chúng tôi đã gộp nó vào một phương thức chuyên dụng rõ ràng để sử dụng lại.
Đây là cách nó sẽ trông như thế nào để áp dụng nó:
someEventRaisingObject.TheEvent += EventHandlers.TryAsync<EventArgs>( async (s, e) => { Console.WriteLine("Starting the event handler..."); await SomeTaskToAwait(); Console.WriteLine("Event handler completed."); }, ex => Console.WriteLine($"[TryAsync Error Callback] Our exception handler caught: {ex}"));
Chúng tôi có thể thấy rằng chúng tôi hiện có một đại biểu có chữ ký Tác vụ không đồng bộ để làm việc và bất kỳ thứ gì chúng tôi đặt bên trong chữ ký đó mà chúng tôi yên tâm sẽ có thử/bắt xung quanh nó trong phương thức của trình trợ giúp mà chúng tôi đã thấy trước đó.
Đây là ảnh chụp màn hình hiển thị lệnh gọi lại của trình xử lý lỗi nắm bắt chính xác ngoại lệ:
Ưu điểm:
Nhược điểm:
Trong khi ban đầu tôi bắt đầu khám phá
Trong bài viết này, chúng ta đã khám phá điều mà tôi có thể tranh luận là cách đơn giản nhất để làm cho Trình xử lý sự kiện không đồng bộ của bạn hoạt động đúng cách và giải pháp tinh chỉnh (theo ý kiến của tôi) chỉ có nhược điểm mà bạn cần nhớ để sử dụng nó.
Một người bình luận đã gợi ý rằng người ta có thể khám phá
Có một số khung AoP thời gian biên dịch tồn tại, nhưng tôi sẽ để nó như một bài tập cho bạn với tư cách là người đọc (vì đó cũng là một bài tập để tôi tiếp tục theo dõi).