2010-01-07 42 views
7

Tôi đang xây dựng thành phần không trực quan trong .Net 2.0. Thành phần này sử dụng một ổ cắm không đồng bộ (BeginReceive, EndReceive, vv). Gọi lại không đồng bộ được gọi trong ngữ cảnh của một chuỗi công nhân được tạo ra bởi thời gian chạy. Người dùng thành phần không phải lo lắng về đa luồng (Đây là mục tiêu chính, những gì tôi muốn)Sự kiện cháy từ thành phần Không đồng bộ trong chuỗi giao diện người dùng

Người dùng thành phần có thể tạo thành phần không trực quan của tôi trong bất kỳ chủ đề nào (chuỗi giao diện người dùng chỉ là một chủ đề chung cho đơn giản Các ứng dụng nghiêm trọng hơn có thể tạo thành phần trong một chuỗi công nhân tùy ý). Thành phần kích hoạt các sự kiện như "SessionConnected" hoặc "DataAvailable".

Vấn đề: vì các cuộc gọi lại Async và các sự kiện được nêu trong đó trình xử lý sự kiện được thực hiện trong ngữ cảnh chuỗi công nhân. Tôi muốn sử dụng một lớp trung gian buộc trình xử lý sự kiện thực thi trong ngữ cảnh của chuỗi tạo thành phần ở vị trí đầu tiên.

Ví dụ mã (rút gọn từ ngoại lệ xử lý vv ...)

/// <summary> 
    /// Occurs when the connection is ended 
    /// </summary> 
    /// <param name="ar">The IAsyncResult to read the information from</param> 
    private void EndConnect(IAsyncResult ar) 
    { 
     // pass connection status with event 
     this.Socket.EndConnect(ar); 

     this.Stream = new NetworkStream(this.Socket); 

     // -- FIRE CONNECTED EVENT HERE -- 

     // Setup Receive Callback 
     this.Receive(); 
    } 


    /// <summary> 
    /// Occurs when data receive is done; when 0 bytes were received we can assume the connection was closed so we should disconnect 
    /// </summary> 
    /// <param name="ar">The IAsyncResult that was used by BeginRead</param> 
    private void EndReceive(IAsyncResult ar) 
    { 
     int nBytes; 
     nBytes = this.Stream.EndRead(ar); 
     if (nBytes > 0) 
     { 
      // -- FIRE RECEIVED DATA EVENT HERE -- 

      // Setup next Receive Callback 
      if (this.Connected) 
       this.Receive(); 
     } 
     else 
     { 
      this.Disconnect(); 
     } 
    } 

Do tính chất của Async ổ tất cả các ứng dụng sử dụng thành phần của tôi được rải rác với "Nếu (this.InvokeRequired) {... "và tất cả những gì tôi muốn là người dùng có thể sử dụng thành phần của tôi không phải lo lắng như một loại trình đơn thả xuống.

Vì vậy, làm cách nào để tôi nâng cao sự kiện mà không yêu cầu người dùng kiểm tra InvokeRequired (hoặc, đặt khác, làm cách nào để buộc các sự kiện được nêu trong cùng một luồng với chuỗi bắt đầu sự kiện ở vị trí đầu tiên)?

Tôi đã đọc nội dung về AsyncOperation, BackgroundWorkers, SynchronizingObjects, AsyncCallbacks và hàng tấn công cụ khác nhưng tất cả đều làm cho đầu của tôi quay.

tôi đã đưa ra với điều này, chắc chắn vụng về, "giải pháp" nhưng có vẻ như thất bại trong một số trường hợp (khi thành phần của tôi được gọi từ một dự án WinForms qua một lớp tĩnh ví dụ)

/// <summary> 
    /// Raises an event, ensuring BeginInvoke is called for controls that require invoke 
    /// </summary> 
    /// <param name="eventDelegate"></param> 
    /// <param name="args"></param> 
    /// <remarks>http://www.eggheadcafe.com/articles/20060727.asp</remarks> 
    protected void RaiseEvent(Delegate eventDelegate, object[] args) 
    { 
     if (eventDelegate != null) 
     { 
      try 
      { 
       Control ed = eventDelegate.Target as Control; 
       if ((ed != null) && (ed.InvokeRequired)) 
        ed.Invoke(eventDelegate, args); 
       else 
        eventDelegate.DynamicInvoke(args); 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine(ex.GetType()); 
       Console.WriteLine(ex.Message); 
       //Swallow 
      } 
     } 
    } 

Bất kỳ giúp đỡ sẽ được đánh giá cao. Cảm ơn trước!

EDIT: Theo this thread đặt cược tốt nhất của tôi là sử dụng SyncrhonizationContext.Post nhưng tôi không thể xem cách áp dụng cho tình huống của mình.

Trả lời

2

Ok; vì vậy, đây là những gì tôi đã kết thúc sau khi đọc thêm một số chi tiết:

public class MyComponent { 
    private AsyncOperation _asyncOperation; 

    /// Constructor of my component: 
    MyComponent() { 
     _asyncOperation = AsyncOperationManager.CreateOperation(null); 
    } 

    /// <summary> 
    /// Raises an event, ensuring the correct context 
    /// </summary> 
    /// <param name="eventDelegate"></param> 
    /// <param name="args"></param> 
    protected void RaiseEvent(Delegate eventDelegate, object[] args) 
    { 
     if (eventDelegate != null) 
     { 
      _asyncOperation.Post(new System.Threading.SendOrPostCallback(
       delegate(object argobj) 
       { 
        eventDelegate.DynamicInvoke(argobj as object[]); 
       }), args); 
     } 
    } 
} 

Giải pháp khác được đăng ở đây là loại công việc đang tiến hành. Các giải pháp được đăng ở đây dường như (theo MSDN) là tốt nhất cho đến nay. Gợi ý rất, rất được hoan nghênh.

0

Có lẽ tôi không hiểu vấn đề, nhưng có vẻ như với tôi rằng bạn chỉ có thể chuyển một tham chiếu đến một đối tượng tùy chỉnh trong trạng thái Async của bạn.

Tôi tổng hợp ví dụ sau đây để minh họa;

Đầu tiên chúng tôi có đối tượng gọi lại. Điều này có 2 thuộc tính - một điều khiển để gửi các hành động và một hành động để gọi;

public class Callback 
{ 
    public Control Control { get; set; } 
    public Action Method { get; set; } 
} 

Sau đó, tôi có một dự án WinForms gọi một số mã ngẫu nhiên trên một chuỗi khác (sử dụng BeginInvoke) và sau đó hiển thị hộp thư khi mã kết thúc thực thi.

private void Form1_Load(object sender, EventArgs e) 
    { 
     Action<bool> act = (bool myBool) => 
      { 
       Thread.Sleep(5000); 
      }; 

     act.BeginInvoke(true, new AsyncCallback((IAsyncResult result) => 
     { 
      Callback c = result.AsyncState as Callback; 
      c.Control.Invoke(c.Method); 

     }), new Callback() 
     { 
      Control = this, 
      Method =() => { ShowMessageBox(); } 
     });    
    } 

Phương pháp ShowMessageBox phải chạy trên thread UI và trông giống như:

private void ShowMessageBox() 
    { 
     MessageBox.Show("Testing"); 
    } 

Đây có phải là những gì bạn đang tìm kiếm?

+0

Không thực sự; đây là cách phức tạp cho mọi người khi sử dụng thành phần của tôi. Tất cả việc cần làm là tham khảo thành phần và mã sử dụng như: // Khởi tạo MyComponent bằng IP, cổng, v.v. MyComponent.SendString ("Điều này thật tuyệt"); MyComponent tăng sự kiện; cho dù chúng được xử lý bởi một dự án GUI hay không GUI không quan trọng và tôi không muốn người dùng chịu trách nhiệm kiểm tra InvokeRequired. – ComponentBuilder

0

Nếu thành phần của bạn luôn luôn phải được sử dụng bởi các chủ đề tương tự, bạn có thể làm một cái gì đó như thế này:

public delegate void CallbackInvoker(Delegate method, params object[] args); 

public YourComponent(CallbackInvoker invoker) 
{ 
    m_invoker = invoker; 
} 

protected void RaiseEvent(Delegate eventDelegate, object[] args) 
{ 
    if (eventDelegate != null) 
    { 
     try 
     { 
      if (m_invoker != null) 
       m_invoker(eventDelegate, args); 
      else 
       eventDelegate.DynamicInvoke(args); 
     } 
     catch (Exception ex) 
     { 
      Console.WriteLine(ex.GetType()); 
      Console.WriteLine(ex.Message); 
      //Swallow 
     } 
    } 
} 

Sau đó, khi bạn nhanh chóng thành phần của bạn từ một mẫu hoặc điều khiển khác mà bạn có thể làm điều này:

YourComponent c = new YourComponent(this.Invoke); 

Để xếp hàng sự kiện trên chuỗi công việc không phải UI, nó phải có một số loại cơ chế xếp hàng công việc, sau đó bạn có thể đưa ra một phương thức có chữ ký của CallbackInvoker để xếp hàng đại biểu trên chuỗi công nhân.

+0

Điều này đặt quá phức tạp trong lòng người dùng sử dụng thành phần của tôi. – ComponentBuilder

+0

Sự phức tạp đó ở đâu? Chỉ định một đại biểu trong constructor của thành phần? –

+0

Tôi muốn thành phần của tôi được minh bạch Không đồng bộ (do đó người dùng cuối không nhận thấy). Khi người dùng cuối phải vượt qua một đại biểu không có lý do (rõ ràng) tôi nghĩ rằng nó không phải là 'thân thiện'. Tôi dường như đã tìm thấy giải pháp của tôi mặc dù; nhưng tôi không chắc đó có phải là giải pháp "tốt nhất" hay không. – ComponentBuilder

1

tôi dường như đã tìm thấy giải pháp của tôi:

private SynchronizationContext _currentcontext 

    /// Constructor of my component: 
    MyComponent() { 
     _currentcontext = WindowsFormsSynchronizationContext.Current; 
     //...or...? 
     _currentcontext = SynchronizationContext.Current; 
    } 

    /// <summary> 
    /// Raises an event, ensuring the correct context 
    /// </summary> 
    /// <param name="eventDelegate"></param> 
    /// <param name="args"></param> 
    protected void RaiseEvent(Delegate eventDelegate, object[] args) 
    { 
     if (eventDelegate != null) 
     { 
      if (_currentcontext != null) 
       _currentcontext.Post(new System.Threading.SendOrPostCallback(
        delegate(object a) 
        { 
         eventDelegate.DynamicInvoke(a as object[]); 
        }), args); 
      else 
       eventDelegate.DynamicInvoke(args); 
     } 
    } 

tôi vẫn đang thử nghiệm này nhưng có vẻ như để làm việc tốt.

+0

Điều gì sẽ xảy ra với phương pháp này khi thành phần của bạn không được tạo trên chuỗi giao diện người dùng? –

+0

Tôi quên dán bản chỉnh sửa mới nhất của mình, nó sẽ kiểm tra nếu _currentcontext là null; điều này hiện đã được sửa (và chỉnh sửa). – ComponentBuilder

+0

Tôi vừa kiểm tra, nếu bạn nắm bắt SynchronizationContext trong constructor của thành phần và thành phần được tạo ra trong một thread UI không, eventDelegate của bạn sẽ được thực hiện trên ThreadPool. –

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