Database
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Database

Sự kiện và Chủ đề trong .NET

Tôi muốn nói thẳng với bạn rằng bài viết này sẽ không liên quan cụ thể đến các chuỗi mà là các sự kiện trong ngữ cảnh của các chuỗi trong .NET. Vì vậy, tôi sẽ không cố gắng sắp xếp các chuỗi một cách chính xác (với tất cả các lần chặn, gọi lại, hủy, v.v.) Có rất nhiều bài viết về chủ đề này.

Tất cả các ví dụ được viết bằng C # cho phiên bản khung 4.0 (trong phiên bản 4.6, mọi thứ dễ dàng hơn một chút, nhưng vẫn có nhiều dự án trong 4.0). Tôi cũng sẽ cố gắng bám sát C # phiên bản 5.0.

Đầu tiên, tôi muốn lưu ý rằng có những người đại diện sẵn sàng cho hệ thống sự kiện .Net mà tôi thực sự khuyên bạn nên sử dụng thay vì phát minh ra thứ gì đó mới. Ví dụ:tôi thường xuyên gặp phải 2 phương pháp sau để tổ chức sự kiện.

Phương pháp đầu tiên:

 class WrongRaiser
    {
        public event Action<object> MyEvent;
        public event Action MyEvent2;
    }

Tôi khuyên bạn nên sử dụng phương pháp này một cách cẩn thận. Nếu bạn không phổ cập nó, cuối cùng bạn có thể viết nhiều mã hơn mong đợi. Do đó, nó sẽ không thiết lập một cấu trúc chính xác hơn nếu so sánh với các phương pháp bên dưới.

Từ kinh nghiệm của mình, tôi có thể nói rằng tôi đã sử dụng nó khi bắt đầu làm việc với các sự kiện và do đó đã tự biến mình thành một trò ngốc. Bây giờ, tôi sẽ không bao giờ làm cho nó xảy ra.

Phương pháp thứ hai:

    class WrongRaiser
    {
        public event MyDelegate MyEvent;
    }

    class MyEventArgs
    {
        public object SomeProperty { get; set; }
    }

    delegate void MyDelegate(object sender, MyEventArgs e);

Phương pháp này khá hợp lệ, nhưng nó tốt cho các trường hợp cụ thể khi phương pháp dưới đây không hoạt động vì một số lý do. Nếu không, bạn có thể nhận được rất nhiều công việc đơn điệu.

Và bây giờ, hãy xem những gì đã được tạo cho các sự kiện.

Phương pháp phổ biến:

    class Raiser
    {
        public event EventHandler<MyEventArgs> MyEvent;
    }

    class MyEventArgs : EventArgs
    {
        public object SomeProperty { get; set; }
    }

Như bạn có thể thấy, ở đây chúng tôi sử dụng lớp EventHandler phổ quát. Có nghĩa là, không cần phải xác định trình xử lý của riêng bạn.

Các ví dụ khác mô tả phương pháp phổ quát.

Hãy xem ví dụ đơn giản nhất về trình tạo sự kiện.

    class EventRaiser
    {
        int _counter;

        public event EventHandler<EventRaiserCounterChangedEventArgs> CounterChanged;

        public int Counter
        {
            get
            {
                return _counter;
            }

            set
            {
                if (_counter != value)
                {
                    var old = _counter;
                    _counter = value;
                    OnCounterChanged(old, value);
                }
            }
        }

        public void DoWork()
        {
            new Thread(new ThreadStart(() =>
            {
                for (var i = 0; i < 10; i++)
                    Counter = i;
            })).Start();
        }

        void OnCounterChanged(int oldValue, int newValue)
        {
            if (CounterChanged != null)
                CounterChanged.Invoke(this, new EventRaiserCounterChangedEventArgs(oldValue, newValue));
        }
    }

    class EventRaiserCounterChangedEventArgs : EventArgs
    {
        public int NewValue { get; set; }
        public int OldValue { get; set; }
        public EventRaiserCounterChangedEventArgs(int oldValue, int newValue)
        {
            NewValue = newValue;
            OldValue = oldValue;
        }
    }

Ở đây chúng ta có một lớp với thuộc tính Counter có thể được thay đổi từ 0 thành 10. Tại đó, logic thay đổi Counter được xử lý trong một luồng riêng.

Và đây là điểm vào của chúng tôi:

    class Program
    {
        static void Main(string[] args)
        {
            var raiser = new EventRaiser();
            raiser.CounterChanged += Raiser_CounterChanged;
            raiser.DoWork();
            Console.ReadLine();
        }

        static void Raiser_CounterChanged(object sender, EventRaiserCounterChangedEventArgs e)
        {
            Console.WriteLine(string.Format("OldValue: {0}; NewValue: {1}", e.OldValue, e.NewValue));
        }
    }

Đó là, chúng tôi tạo một phiên bản của trình tạo của chúng tôi, đăng ký thay đổi bộ đếm và, trong trình xử lý sự kiện, xuất các giá trị ra bảng điều khiển.

Đây là kết quả chúng tôi nhận được:

Càng xa càng tốt. Nhưng chúng ta hãy nghĩ xem, trình xử lý sự kiện được thực thi trong luồng nào?

Hầu hết các đồng nghiệp của tôi đã trả lời câu hỏi này "Nói chung là một". Có nghĩa là không ai trong số họ không hiểu cách sắp xếp các đại biểu. Tôi sẽ cố gắng giải thích nó.

Lớp Delegate chứa thông tin về một phương thức.

Ngoài ra còn có hậu duệ của nó, MulticastDelegate, có nhiều hơn một phần tử.

Vì vậy, khi bạn đăng ký một sự kiện, một phiên bản con của MulticastDelegate sẽ được tạo. Mỗi người đăng ký tiếp theo sẽ thêm một phương thức mới (trình xử lý sự kiện) vào phiên bản MulticastDelegate đã được tạo sẵn.

Khi bạn gọi phương thức Gọi, các trình xử lý của tất cả người đăng ký được gọi lần lượt cho sự kiện của bạn. Tại thời điểm đó, chuỗi mà bạn gọi các trình xử lý này không biết một điều gì về chuỗi mà chúng đã được chỉ định và tương ứng, nó không thể chèn bất cứ thứ gì vào chuỗi đó.

Nói chung, các trình xử lý sự kiện trong ví dụ trên được thực thi trong luồng được tạo trong phương thức DoWork (). Có nghĩa là, trong quá trình tạo sự kiện, luồng đã tạo ra nó theo cách như vậy, đang đợi tất cả các trình xử lý thực thi. Tôi sẽ cho bạn thấy điều này mà không cần rút Id chủ đề. Đối với điều này, tôi đã thay đổi một vài dòng mã trong ví dụ trên.

Bằng chứng rằng tất cả các trình xử lý trong ví dụ trên đều được thực thi trong luồng được gọi là sự kiện

Phương pháp tạo sự kiện

        void OnCounterChanged(int oldValue, int newValue)
        {
            if (CounterChanged != null)
            {
                CounterChanged.Invoke(this, new EventRaiserCounterChangedEventArgs(oldValue, newValue));
                Console.WriteLine(string.Format("Event Raiser: old = {0}, new = {1}", oldValue, newValue));
            }
                
        }

Người xử lý

        static void Raiser_CounterChanged(object sender, EventRaiserCounterChangedEventArgs e)
        {
            Console.WriteLine(string.Format("OldValue: {0}; NewValue: {1}", e.OldValue, e.NewValue));
            Thread.Sleep(500);
        }

Trong trình xử lý, chúng tôi gửi luồng hiện tại ở trạng thái ngủ trong nửa giây. Nếu các trình xử lý hoạt động trong luồng chính, thời gian này sẽ đủ để một luồng được tạo trong DoWork () hoàn thành công việc và xuất ra kết quả của nó.

Tuy nhiên, đây là những gì chúng ta thực sự thấy:

Tôi không biết ai và làm thế nào sẽ xử lý các sự kiện được tạo bởi lớp tôi đã viết, nhưng tôi thực sự không muốn những người xử lý này làm chậm công việc của lớp tôi. Đó là lý do tại sao, tôi sẽ sử dụng phương thức BeginInvoke thay vì Invoke. BeginInvoke tạo một chuỗi mới.

Lưu ý:Cả hai phương thức Invoke và BeginInvoke không phải là thành viên của các lớp Delegate hoặc MulticastDelegate. Chúng là các thành viên của lớp đã tạo (hoặc lớp phổ quát được mô tả ở trên).

Bây giờ, nếu chúng ta thay đổi phương thức tạo sự kiện, chúng ta sẽ nhận được như sau:

Tạo sự kiện đa luồng:

        void OnCounterChanged(int oldValue, int newValue)
        {
            if (CounterChanged != null)
            {
                var delegates = CounterChanged.GetInvocationList();
                for (var i = 0; i < delegates.Length; i++)
                    ((EventHandler<EventRaiserCounterChangedEventArgs>)delegates[i]).BeginInvoke(this, new EventRaiserCounterChangedEventArgs(oldValue, newValue), null, null);
                Console.WriteLine(string.Format("Event Raiser: old = {0}, new = {1}", oldValue, newValue));
            }
                
        }

Hai tham số cuối cùng bằng null. Cái đầu tiên là một cuộc gọi lại, cái thứ hai là một tham số nhất định. Tôi không sử dụng callback trong ví dụ này, vì ví dụ này là trung gian. Nó có thể hữu ích cho phản hồi. Ví dụ:nó có thể giúp lớp tạo ra sự kiện xác định xem một sự kiện có được xử lý hay không và / hoặc nếu nó được yêu cầu để nhận kết quả của việc xử lý này. Nó cũng có thể giải phóng các tài nguyên liên quan đến hoạt động không đồng bộ.

Nếu chúng ta chạy chương trình, chúng ta sẽ nhận được kết quả sau.

Tôi đoán là khá rõ ràng rằng hiện nay các trình xử lý sự kiện được thực thi trong các luồng riêng biệt, tức là trình tạo sự kiện không quan tâm đến việc ai, cách thức và thời gian sẽ xử lý các sự kiện của nó.

Và ở đây câu hỏi đặt ra:còn xử lý tuần tự thì sao? Rốt cuộc thì chúng ta có Counter. Điều gì sẽ xảy ra nếu nó là một sự thay đổi trạng thái nối tiếp? Nhưng tôi sẽ không trả lời câu hỏi này, nó không phải là chủ đề của bài viết này. Tôi chỉ có thể nói rằng có một số cách.

Và một điều nữa. Để không lặp đi lặp lại các hành động giống nhau, tôi khuyên bạn nên tạo một lớp riêng cho chúng.

Một lớp để tạo sự kiện không đồng bộ

    static class AsyncEventsHelper
    {
        public static void RaiseEventAsync<T>(EventHandler<T> h, object sender, T e) where T : EventArgs
        {
            if (h != null)
            {
                var delegates = h.GetInvocationList();
                for (var i = 0; i < delegates.Length; i++)
                    ((EventHandler<T>)delegates[i]).BeginInvoke(sender, e, h.EndInvoke, null);
            }
        }
    }

Trong trường hợp này, chúng tôi sử dụng callback. Nó được thực thi trong cùng một luồng với trình xử lý. Nghĩa là, sau khi hoàn thành phương thức xử lý, ủy nhiệm gọi h.EndInvoke tiếp theo.

Đây là cách nên được sử dụng

        void OnCounterChanged(int oldValue, int newValue)
        {
            AsyncEventsHelper.RaiseEventAsync(CounterChanged, this, new EventRaiserCounterChangedEventArgs(oldValue, newValue)); 
        }

Tôi đoán, bây giờ đã rõ tại sao lại cần đến phương pháp phổ quát. Nếu chúng tôi mô tả các sự kiện bằng phương pháp 2, thủ thuật này sẽ không hoạt động. Nếu không, bạn sẽ phải tự mình tạo ra tính phổ quát cho các đại biểu của mình.

Lưu ý :Đối với các dự án thực, tôi khuyên bạn nên thay đổi kiến ​​trúc sự kiện trong ngữ cảnh của các luồng. Các ví dụ được mô tả có thể gây thiệt hại cho công việc của ứng dụng với các luồng và chỉ được cung cấp cho mục đích cung cấp thông tin.

Kết luận

Hy vọng, tôi đã quản lý để mô tả cách các sự kiện hoạt động và nơi các trình xử lý hoạt động. Trong bài viết tiếp theo, tôi dự định đi sâu vào việc nhận kết quả của việc xử lý sự kiện khi một cuộc gọi không đồng bộ được thực hiện.

Tôi mong nhận được ý kiến ​​và đề xuất của bạn.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Các vấn đề về hiệu suất:Cuộc gặp gỡ đầu tiên

  2. Làm thế nào để tạo các thủ tục được lưu trữ trong SQL?

  3. Các lệnh RMAN không thành công với ORA-00904:“BS”. ”GUID”:mã định danh không hợp lệ

  4. Vui lòng trợ giúp với các cải tiến STRING_SPLIT

  5. SQL CREATE TABLE… AS Câu lệnh CHỌN