2008-08-08 26 views
32

Khi bạn đăng ký một sự kiện trên một đối tượng từ bên trong một biểu mẫu, bạn chủ yếu bàn giao quyền kiểm soát phương thức gọi lại cho nguồn sự kiện. Bạn không có ý tưởng liệu nguồn sự kiện đó có chọn kích hoạt sự kiện trên một chuỗi khác không.Làm cách nào để tạo các cuộc gọi lại sự kiện vào chuỗi biểu mẫu giành chiến thắng của tôi an toàn?

Vấn đề là khi gọi lại được gọi, bạn không thể giả định rằng bạn có thể thực hiện điều khiển cập nhật trên biểu mẫu của bạn bởi vì đôi khi những điều khiển đó sẽ ném một sự kiện nếu cuộc gọi lại được gọi trên chuỗi khác với chuỗi chạy trên.

Trả lời

31

Để đơn giản hóa mã của Simon một chút, bạn có thể sử dụng ủy nhiệm Hành động chung chung. Nó tiết kiệm peppering mã của bạn với một loạt các loại đại biểu bạn không thực sự cần. Ngoài ra, trong .NET 3.5, họ đã thêm một tham số params vào phương thức Invoke, do đó bạn không phải định nghĩa một mảng tạm thời.

void SomethingHappened(object sender, EventArgs ea) 
{ 
    if (InvokeRequired) 
    { 
     Invoke(new Action<object, EventArgs>(SomethingHappened), sender, ea); 
     return; 
    } 

    textBox1.Text = "Something happened"; 
} 
15

Dưới đây là những điểm nổi bật:

  1. Bạn không thể thực hiện cuộc gọi điều khiển UI từ một thread khác nhau so với cái chúng được tạo ra trên (Chủ đề của mẫu).
  2. Yêu cầu ủy nhiệm (tức là móc sự kiện) được kích hoạt trên cùng một chuỗi với đối tượng đang kích hoạt sự kiện.

Vì vậy, nếu bạn có một chuỗi "động cơ" riêng biệt thực hiện một số công việc và xem một số giao diện người dùng. Cháy của động cơ là một sự kiện thay đổi đối tượng đã được nối bởi Biểu mẫu. Nhưng đại biểu gọi lại rằng Biểu mẫu đã đăng ký với công cụ được gọi trên luồng của động cơ… chứ không phải trên luồng của Biểu mẫu. Và do đó bạn không thể cập nhật bất kỳ điều khiển nào từ cuộc gọi lại đó. Doh!

BeginInvoke đến để giải cứu. Chỉ cần sử dụng mô hình mã hóa đơn giản này trong tất cả các phương thức gọi lại của bạn và bạn có thể chắc chắn rằng mọi thứ sẽ ổn thỏa:

private delegate void EventArgsDelegate(object sender, EventArgs ea); 

void SomethingHappened(object sender, EventArgs ea) 
{ 
    // 
    // Make sure this callback is on the correct thread 
    // 
    if (this.InvokeRequired) 
    { 
     this.Invoke(new EventArgsDelegate(SomethingHappened), new object[] { sender, ea }); 
     return; 
    } 

    // 
    // Do something with the event such as update a control 
    // 
    textBox1.Text = "Something happened"; 
} 

Thực sự khá đơn giản.

  1. Sử dụng InvokeRequired để tìm hiểu xem cuộc gọi lại này có xảy ra đúng chuỗi không.
  2. Nếu không, sau đó reinvoke gọi lại trên các chủ đề chính xác với các thông số tương tự. Bạn có thể reinvoke một phương pháp bằng cách sử dụng các phương thức Gọi (chặn) hoặc BeginInvoke (không chặn).
  3. Lần sau khi hàm được gọi, InvokeRequired trả về false vì chúng tôi đang ở đúng chuỗi và mọi người đều hài lòng.

Đây là một cách rất nhỏ gọn để giải quyết vấn đề này và làm cho Biểu mẫu của bạn an toàn từ cuộc gọi lại nhiều sự kiện.

+1

Tôi thường thích BeginInvoke để Gọi, nhưng có một báo trước: người ta phải tránh xếp hàng quá nhiều sự kiện. Tôi sử dụng một biến updateRequired được thiết lập để 1 khi một BeginInvoke sẽ xảy ra, và chỉ thực hiện BeginInvoke nếu nó đã bằng không (sử dụng Interlocked.Exchange). Trình xử lý hiển thị có một vòng lặp while xóa updateRequired và, nếu nó không phải là 0, thực hiện cập nhật và các vòng lặp. Trong một số trường hợp, bộ hẹn giờ được thêm vào để giới hạn tần suất cập nhật thêm (để tránh việc sử dụng mã toàn bộ thời gian cập nhật dữ liệu tiến trình thay vì thực hiện công việc thực tế) nhưng điều đó phức tạp hơn. – supercat

+0

@Supercat ... điều chỉnh sự kiện là một chủ đề quan trọng cho nhiều ứng dụng, nhưng nó không phải là một cái gì đó mà nên là một phần của lớp giao diện người dùng. Một bus proxy sự kiện riêng biệt sẽ được tạo để nhận, xếp hàng, kết hợp và gửi lại các sự kiện vào các khoảng thời gian thích hợp. Bất kỳ thuê bao nào vào bus sự kiện không nên biết rằng sự kiện điều chỉnh đang xảy ra. –

+0

Tôi có thể thấy những nơi mà một "bus sự kiện" riêng biệt để xử lý đồng bộ hóa có thể hữu ích, nhưng trong nhiều trường hợp có vẻ dễ dàng nhất đối với người dùng cuối giống như lớp chỉ báo tiến trình nếu lớp chỉ đơn giản là tiếp xúc với thuộc tính MinimumUpdateInterval. – supercat

0

Trong nhiều trường hợp đơn giản, bạn có thể sử dụng ủy nhiệm MethodInvoker và tránh sự cần thiết phải tạo loại đại biểu của riêng bạn.

9

tôi sử dụng phương pháp vô danh rất nhiều trong kịch bản này:

void SomethingHappened(object sender, EventArgs ea) 
{ 
    MethodInvoker del = delegate{ textBox1.Text = "Something happened"; }; 
    InvokeRequired ? Invoke(del) : del(); 
} 
2

tôi là một chút muộn để chủ đề này, nhưng bạn có thể muốn có một cái nhìn tại Event-Based Asynchronous Pattern. Khi được triển khai đúng cách, nó đảm bảo rằng các sự kiện luôn được nâng lên từ chuỗi giao diện người dùng.

Dưới đây là một ví dụ ngắn gọn chỉ cho phép một yêu cầu đồng thời; hỗ trợ nhiều invocations/sự kiện đòi hỏi một chút đường ống dẫn nước.

using System; 
using System.ComponentModel; 
using System.Threading; 
using System.Windows.Forms; 

namespace WindowsFormsApplication1 
{ 
    public class MainForm : Form 
    { 
     private TypeWithAsync _type; 

     [STAThread()] 
     public static void Main() 
     { 
      Application.EnableVisualStyles(); 
      Application.Run(new MainForm()); 
     } 

     public MainForm() 
     { 
      _type = new TypeWithAsync(); 
      _type.DoSomethingCompleted += DoSomethingCompleted; 

      var panel = new FlowLayoutPanel() { Dock = DockStyle.Fill }; 

      var btn = new Button() { Text = "Synchronous" }; 
      btn.Click += SyncClick; 
      panel.Controls.Add(btn); 

      btn = new Button { Text = "Asynchronous" }; 
      btn.Click += AsyncClick; 
      panel.Controls.Add(btn); 

      Controls.Add(panel); 
     } 

     private void SyncClick(object sender, EventArgs e) 
     { 
      int value = _type.DoSomething(); 
      MessageBox.Show(string.Format("DoSomething() returned {0}.", value)); 
     } 

     private void AsyncClick(object sender, EventArgs e) 
     { 
      _type.DoSomethingAsync(); 
     } 

     private void DoSomethingCompleted(object sender, DoSomethingCompletedEventArgs e) 
     { 
      MessageBox.Show(string.Format("DoSomethingAsync() returned {0}.", e.Value)); 
     } 
    } 

    class TypeWithAsync 
    { 
     private AsyncOperation _operation; 

     // synchronous version of method 
     public int DoSomething() 
     { 
      Thread.Sleep(5000); 
      return 27; 
     } 

     // async version of method 
     public void DoSomethingAsync() 
     { 
      if (_operation != null) 
      { 
       throw new InvalidOperationException("An async operation is already running."); 
      } 

      _operation = AsyncOperationManager.CreateOperation(null); 
      ThreadPool.QueueUserWorkItem(DoSomethingAsyncCore); 
     } 

     // wrapper used by async method to call sync version of method, matches WaitCallback so it 
     // can be queued by the thread pool 
     private void DoSomethingAsyncCore(object state) 
     { 
      int returnValue = DoSomething(); 
      var e = new DoSomethingCompletedEventArgs(returnValue); 
      _operation.PostOperationCompleted(RaiseDoSomethingCompleted, e); 
     } 

     // wrapper used so async method can raise the event; matches SendOrPostCallback 
     private void RaiseDoSomethingCompleted(object args) 
     { 
      OnDoSomethingCompleted((DoSomethingCompletedEventArgs)args); 
     } 

     private void OnDoSomethingCompleted(DoSomethingCompletedEventArgs e) 
     { 
      var handler = DoSomethingCompleted; 

      if (handler != null) { handler(this, e); } 
     } 

     public EventHandler<DoSomethingCompletedEventArgs> DoSomethingCompleted; 
    } 

    public class DoSomethingCompletedEventArgs : EventArgs 
    { 
     private int _value; 

     public DoSomethingCompletedEventArgs(int value) 
      : base() 
     { 
      _value = value; 
     } 

     public int Value 
     { 
      get { return _value; } 
     } 
    } 
} 
+1

Tôi nghĩ rằng có một chút sai lầm khi nói 'nó đảm bảo rằng các sự kiện luôn được nâng lên từ chuỗi giao diện người dùng'. Nó sẽ không chính xác hơn khi nói rằng nó đảm bảo rằng trình xử lý sự kiện được thực hiện trên cùng một SynchronizationContext/thread mà tác vụ được tạo ra? (Mà có thể không phải là chủ đề UI/SynchronizationContext) – jspaey

1

lazy programmer, tôi có phương pháp rất lười.

Điều tôi làm chỉ đơn giản là điều này.

private void DoInvoke(MethodInvoker del) { 
    if (InvokeRequired) { 
     Invoke(del); 
    } else { 
     del(); 
    } 
} 
//example of how to call it 
private void tUpdateLabel(ToolStripStatusLabel lbl, String val) { 
    DoInvoke(delegate { lbl.Text = val; }); 
} 

Bạn có thể nội tuyến DoInvoke bên trong chức năng của mình hoặc ẩn nó trong chức năng riêng biệt để thực hiện công việc bẩn cho bạn.

Chỉ cần nhớ rằng bạn có thể chuyển trực tiếp các hàm vào phương thức DoInvoke.

private void directPass() { 
    DoInvoke(this.directInvoke); 
} 
private void directInvoke() { 
    textLabel.Text = "Directly passed."; 
} 
+0

Tôi là tất cả cho lập trình lười biếng :) Nếu bạn đang sử dụng .NET 3.5 hoặc cao hơn, bạn có thể sử dụng 'Action' hoặc' Action 'cùng với lambda biểu thức: 'Doinvoke (() => textLabel.Text =" Something ")' –

Các vấn đề liên quan