paint-brush
Mạng lưới an toàn đơn giản cho Trình xử lý sự kiện không đồng bộtừ tác giả@devleader
816 lượt đọc
816 lượt đọc

Mạng lưới an toàn đơn giản cho Trình xử lý sự kiện không đồng bộ

từ tác giả Dev Leader8m2023/02/14
Read on Terminal Reader

dài quá đọc không nổi

Khoảng trống không đồng bộ là ngoại lệ duy nhất mà chúng tôi dường như cho phép thiết lập Trình xử lý sự kiện không đồng bộ đáng sợ. 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à khuyết đ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ó hợp lý hay không.
featured image - Mạng lưới an toàn đơn giản cho Trình xử lý sự kiện không đồng bộ
Dev Leader HackerNoon profile picture

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 thiết lập khoảng trống không đồng bộ đáng sợ .


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 ở đây . Nếu không, bạn có thể kiểm tra mã trên GitHub nếu bạn muốn sao chép nó cục bộ để dùng thử.

Một video đồng hành!

Bấm vào đây để xem video!

Vấn đề

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.


bài báo trước đã giải quyết vấn đề này bằng cách cho phép chúng tôi sáng tạo ở khía cạnh gọi của mọi thứ nhưng…


  • Chúng tôi có thể cần hỗ trợ cho điều này trên các đối tượng mà chúng tôi không kiểm soát việc gọi các sự kiện (nghĩa là bạn đang kết nối với sự kiện nhấp chuột của một nút trong khung giao diện người dùng yêu thích của mình)


  • Một số người coi việc sử dụng bối cảnh bên trong giải pháp đó là một hành vi hack (tôi cũng không đồng ý với điều đó).


  • … Cụ thể, với trình xử lý sự kiện, chúng tôi có một số thủ thuật đơn giản khác mà chúng tôi có thể thực hiện để hỗ trợ Trình xử lý sự kiện không đồng bộ!


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.

Giải quyết Trình xử lý sự kiện không đồng bộ bằng Thử/Bắt

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:

  • Cực kỳ đơn giản. Không có cơ chế phức tạp để hiểu.


  • Không có gói yêu cầu.


  • Bạn không cần phải là chủ sở hữu của lớp gây ra sự kiện để điều này hoạt động. Điều này có nghĩa là phương pháp này sẽ hoạt động đối với tất cả các đối tượng gây sự kiện hiện có bao gồm các thành phần giao diện người dùng WinForms và WPF.


Nhược điểm:

  • Bạn cần nhớ làm điều này… ở mọi nơi.


  • Có thể khi mã của bạn phát triển theo thời gian, ai đó có thể vô tình viết logic bên ngoài tính năng bắt thử của trình xử lý sự kiện có thể đưa ra các ngoại lệ


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ách tiếp cận (hơi) đẹp hơn để cải thiện Trình xử lý sự kiện không đồng bộ

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 ra của chương trình ví dụ cho EventHandlers không đồng bộ

Ưu điểm:


  • Vẫn rất đơn giản. Chức năng của trình bao bọc * phức tạp hơn một chút *, nhưng vẫn rất cơ bản.


  • Không có gói yêu cầu.


  • Bạn không cần phải là chủ sở hữu của lớp gây ra sự kiện để điều này hoạt động. Điều này có nghĩa là phương pháp này sẽ hoạt động đối với tất cả các đối tượng gây sự kiện hiện có bao gồm các thành phần giao diện người dùng WinForms và WPF.


  • Ý định rõ ràng hơn khi làm việc với EventHandlers không đồng bộ vì cú pháp khi kết nối trình xử lý với sự kiện.


  • Sự trôi dạt mã mà cuối cùng ném ra nhiều ngoại lệ hơn sẽ vẫn được bao bọc bên trong thử/bắt


Nhược điểm:


  • Bạn vẫn cần phải nhớ móc thứ này lên!

Kết thúc suy nghĩ về Trình xử lý sự kiện không đồng bộ

Trong khi ban đầu tôi bắt đầu khám phá cách thú vị để đối phó với khoảng trống không đồng bộ , phản hồi của người đọc hợp lệ ở chỗ các ví dụ tập trung vào Trình xử lý sự kiện không đồng bộ và chắc chắn phải có một cách đơn giản hơn.


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á Lập trình hướng khía cạnh (AoP) để đưa loại hành vi này vào ứng dụng của bạn để bạn không cần phải nhớ thực hiện nó.


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).