2010-03-08 1 views
15

Tôi muốn có hai Chủ đề. Hãy gọi họ là:C# Sự kiện giữa các chuỗi được thực hiện trong chuỗi của riêng họ (Cách thực hiện)?

  • Chủ đề Một
  • Thread B

Chủ đề Một bắn một sự kiện và thread B lắng nghe sự kiện này. Khi Trình xử lý sự kiện chủ đề B được thực thi, nó được thực thi với ID luồng của chủ đề A, vì vậy tôi đoán nó được thực thi trong Chủ đề A.

Điều tôi muốn làm là có thể kích hoạt sự kiện cho Chủ đề B nói một cái gì đó như: "hey, một dữ liệu đã sẵn sàng cho bạn, bạn có thể xử lý nó ngay bây giờ". Sự kiện này phải được thực thi trong Chủ đề của chính nó vì nó sử dụng những thứ mà chỉ có anh ta mới có thể truy cập (như các điều khiển UI).

Tôi có thể làm như thế nào?

Cảm ơn sự giúp đỡ của bạn.

Trả lời

11

Bạn sẽ cần sắp xếp lại thông tin vào chuỗi giao diện người dùng.

Thông thường, bạn sẽ xử lý điều này trong Trình xử lý sự kiện của mình. Ví dụ, nói Thread A là giao diện người dùng của bạn - khi nó đăng ký một sự kiện trên một đối tượng trong Thread B, trình xử lý sự kiện sẽ được chạy bên trong Thread B. Tuy nhiên, nó có thể chỉ cần sắp xếp lại thành thread UI:

// In Thread A (UI) class... 
private void myThreadBObject_EventHandler(object sender, EventArgs e) 
{ 
    this.button1.BeginInvoke(new Action(
     () => 
      { 
       // Put your "work" here, and it will happen on the UI thread... 
      })); 
} 
+0

Bạn có thể vui lòng cung cấp tài liệu tham khảo về 'cần sắp xếp lại thông tin', vì tôi biết marshaling là bắt buộc cho quá trình và căn hộ chéo. Tại sao marshal nếu các chủ đề trong cùng một quá trình? (và không có căn hộ vì COM không được sử dụng). – bjan

+0

Tôi đã nhận được câu trả lời của mình ở đây ** [tại đây] (http://stackoverflow.com/questions/1361033/what-does-stathread-do) ** – bjan

6

cách đơn giản nhất có lẽ là để đăng ký sử dụng một trình xử lý sự kiện mà chỉ marshal "thật" gọi handler vào chủ đề B. Ví dụ, xử lý có thể gọi Control.BeginInvoke để làm một số công việc trên thread B:

MethodInvoker realAction = UpdateTextBox; 
foo.SomeEvent += (sender, args) => textBox.BeginInvoke(realAction); 

... 

private void UpdateTextBox() 
{ 
    // Do your real work here 
} 
2

Nếu bạn đang sử dụng Windows Biểu mẫu hoặc WPF và không có tham chiếu Điều khiển có ích từ trình xử lý sự kiện của mình, bạn cũng có thể nắm bắt tham chiếu e trong số System.Threading.SynchronizationContext.Current trong nội dung nào đó đang chạy trên chuỗi giao diện người dùng và hiển thị tham chiếu đó cho trình xử lý sự kiện của bạn. Sau đó, khi bạn cần phải chạy một thứ gì đó trên chuỗi giao diện người dùng, hãy gọi Post() hoặc Send() trên tham chiếu được chụp SynchronizationContext từ trình xử lý sự kiện của bạn, tùy thuộc vào việc bạn muốn chạy nó không đồng bộ hay đồng bộ.

Về cơ bản đây chỉ là đường xung quanh chụp một tài liệu tham khảo Control và gọi Invoke() trên đó, nhưng có thể làm cho mã của bạn đơn giản hơn.

3

Tôi hiện chỉ đang sử dụng C# trong một vài tuần nhưng tôi đã gặp phải cùng một câu hỏi về cách kích hoạt sự kiện trên các chuỗi. Không có nhiều ví dụ hoàn chỉnh có sẵn (bất kỳ?) Và rất khó để hiểu tất cả các phần riêng lẻ từ các chuyên gia khác nhau. Sau một thời gian tôi cuối cùng đã phát triển một cái gì đó mà làm việc, vì vậy tôi muốn chia sẻ một ví dụ hoàn chỉnh của nó về chủ đề này cho bất kỳ người mới đến như bản thân mình. Ngoài ra tôi hoan nghênh mọi chuyên gia tư vấn hoặc chỉ trích vì tôi khá mới đối với C# và chắc chắn điều này có thể được cải thiện.

Đây là ví dụ hoàn chỉnh trừ một biểu mẫu nhỏ với 3 nút và thanh dọc dọc, tất cả đều có tên mặc định. Tạo biểu mẫu trong Nhà thiết kế và ghi đè lên nó bằng lớp TestEvent mà tôi có, sau đó kết nối 3 sự kiện OnClick. Thanh cuộn có thể được sử dụng để chọn chuỗi mà bạn muốn kích hoạt sự kiện qua các nút và sẽ tự động mở rộng khi bạn thay đổi numThreads trong void Main(). Button2 sẽ gửi một sự kiện để tắt thread.

Lớp MyEvent có thể được sử dụng kết hợp với bất kỳ lớp nào triển khai giao diện IMyEventActions. Lớp sử dụng MyEvent sẽ tự động nhận các sự kiện được kích hoạt vào OnSomethingHappened (...). Ngoài ra, lớp instantiating MyEvent có thể đăng ký các sự kiện lớp khác một cách đệ quy. Các sự kiện kích hoạt có thể dễ dàng đạt được thông qua phương thức MyEvent.Fire (...).

// Create a designer form with 3 buttons and a vertical trackbar and overwrite 
//it with "TestEvent" class near bottom of code, then hook up the buttons to 
//button<1/2/3>_OnClick. Event Sibling Subscribing section explains why the 
//first 4 event threads all fire at once. 

using System; 
using System.Windows.Forms; 
using System.Threading; 
using System.Collections.Generic; 
using System.Runtime.InteropServices; 

namespace TestingEventsApplication 
{ 
    using Extensions; 
    public delegate void OnSomethingHappenedDel(MyEventArgs e); 
    public delegate void EventMarshalDel(IMyEventActions sender, MyEventArgs e); 
    static class Program 
    { 

     /// <summary> 
     /// The main entry point for the application. 
     /// </summary> 
     [STAThread] 
     static void Main() 
     { 
      Application.EnableVisualStyles(); 
      Application.SetCompatibleTextRenderingDefault(false); 
      Console.WriteLine("Thread Main is Thread#" + Thread.CurrentThread.ManagedThreadId); 

      //This controls how many threads we want to make for testing 
      int numThreads = 10; 

      QuickSync quickSync = new QuickSync(); 
      MyThread[] myThreads = new MyThread[numThreads]; 
      TestEvent GUI = new TestEvent(myThreads); 
      GUI.TrackbarVal = numThreads-1; 
      for (int i = 0; i < numThreads; i++) 
      { 
       myThreads[i] = new MyThread(); 
       Thread thread = new Thread(delegate() 
       { 
        myThreads[i].Start(quickSync); 
       }); 
       thread.Name = "Thread#" + thread.ManagedThreadId.ToString(); 
       thread.IsBackground = true; 
       thread.Start(); 
       while (!thread.IsAlive || !quickSync.Sync) { Thread.Sleep(1); } 
       myThreads[i].thread = thread; 
       Console.WriteLine(thread.Name + " is alive"); 
       quickSync.Sync = false; 
      } 

      #region Event Sibling Subscribing 
      // ********* Event Sibling Subscribing ********* 
      // Just for example, I will link Thread 0 to thread 1, then 
      // 1->2,2->3,3->4 so when thread 0 receives an event, so will 
      // thread 1, 2, 3, and 4 (Noncommutative.) 
      // Loops are perfectly acceptable and will not result in 
      // eternal events. 
      // e.g. 0->1 + 1->0 is OK, or 0->1 + 1->2 + 2->0... No problem. 
      if (numThreads > 0) 
       myThreads[0].Event.SubscribeMeTo(myThreads[1].Event); 
      //Recursively add thread 2 
      if (numThreads > 1) 
       myThreads[1].Event.SubscribeMeTo(myThreads[2].Event); 
      //Recursively add thread 3 
      if (numThreads > 2) 
       myThreads[2].Event.SubscribeMeTo(myThreads[3].Event); 
      //Recursively add thread 4 
      if (numThreads > 3) 
       myThreads[3].Event.SubscribeMeTo(myThreads[4].Event); 
      #endregion 

      Application.Run(GUI); 
     } 
    } 

    /// <summary> 
    /// Used to determine when a task is complete. 
    /// </summary> 
    public class QuickSync 
    { 
     public bool Sync 
     { 
      get 
      { 
       lock (this) 
        return sync; 
      } 
      set 
      { 
       lock (this) 
        sync = value; 
      } 
     } 

     private bool sync; 
    } 

    /// <summary> 
    /// A class representing the operating body of a Background thread. 
    /// Inherits IMyEventActions. 
    /// </summary> 
    /// <param name="m">a QuickSync boxed bool.</param> 
    public class MyThread : IMyEventActions 
    { 
     /// <summary> 
     /// An reference to the Thread object used by this thread. 
     /// </summary> 
     public Thread thread { get; set; } 

     /// <summary> 
     /// Tracks the MyEvent object used by the thread. 
     /// </summary> 
     public MyEvent Event { get; set;} 

     /// <summary> 
     /// Satisfies IMyEventActions and provides a method to implement 
     /// Event actions 
     /// </summary> 
     public void OnSomethingHappened(MyEventArgs e) 
     { 
      switch ((MyEventArgsFuncs)e.Function) 
      { 
       case MyEventArgsFuncs.Shutdown: 
        Console.WriteLine("Shutdown Event detected... " + Thread.CurrentThread.Name + " exiting"); 
        Event.Close(); 
        break; 
       case MyEventArgsFuncs.SomeOtherEvent: 
        Console.WriteLine("SomeOtherEvent Event detected on " + Thread.CurrentThread.Name); 
        break; 
       case MyEventArgsFuncs.TheLastEvent: 
        Console.WriteLine("TheLastEvent Event detected on " + Thread.CurrentThread.Name); 
        break; 
      } 
     } 

     /// <summary> 
     /// The method used by a thread starting delegate. 
     /// </summary> 
     public void Start(QuickSync quickSync) 
     { 
      //MyEvent inherits from Form which inherits from Control which is 
      //the key to this whole thing working. It is the BeginInvoke method 
      //of Control which allows us to marshal objects between threads, 
      //without it any event handlers would simply fire in the same thread 
      //which they were triggered. We don't want to see this form though 
      //so I've moved it off screen and out of the task bar 
      Event = new MyEvent(); 
      Event.MyEventSender = this; 
      Event.SomethingHappened += new EventMarshalDel(Event.EventMarshal); 
      Event.FormBorderStyle = FormBorderStyle.FixedToolWindow; 
      Event.ShowInTaskbar = false; 
      Event.StartPosition = FormStartPosition.Manual; 
      Event.Location = new System.Drawing.Point(-10000, -10000); 
      Event.Size = new System.Drawing.Size(1, 1); 
      System.Windows.Forms.Application.Idle += new EventHandler(OnApplicationIdle); 
      quickSync.Sync = true; 
      Application.Run(Event); 
     } 

     /// <summary> 
     /// The operating body of the thread. 
     /// </summary> 
     private void OnApplicationIdle(object sender, EventArgs e) 
     { 
      while (this.AppStillIdle) 
      { 
       //Do your threads work here... 
       Console.Write("."); 
       Thread.Sleep(1000); 
      } 
     } 

     /// <summary> 
     /// Monitors the Threads msg procedure to make sure we handle messages. 
     /// </summary> 
     public bool AppStillIdle 
     { 
      get 
      { 
       Win32.NativeMessage msg; 
       return !Win32.PeekMessage(out msg, IntPtr.Zero, 0, 0, 0); 
      } 
     } 
    } 

    /// <summary> 
    /// Houses all of the plumbing necessary to fire cross thread events. 
    /// </summary> 
    public class MyEvent : System.Windows.Forms.Form 
    { 
     /// <summary> 
     /// A reference to the object using this MyEvent, used during recursion. 
     /// </summary> 
     public IMyEventActions MyEventSender { get; set; } 

     /// <summary> 
     /// Lock for somethingHappened delegate access. 
     /// </summary> 
     public readonly object someEventLock = new object(); 

     /// <summary> 
     /// Public access to the event SomethingHappened with a locking 
     /// subscription mechanism for thread safety. 
     /// </summary> 
     public event EventMarshalDel SomethingHappened 
     { 
      add 
      { 
       lock (someEventLock) 
        somethingHappened += value; 
      } 
      remove 
      { 
       lock (someEventLock) //Contributes to preventing race condition 
        somethingHappened -= value; 
      } 
     } 

     /// <summary> 
     /// The trigger of MyEvent class. 
     /// </summary> 
     public void Fire(MyEventArgs e) 
     { 
      //After rigorous testing I found this was the simplest way to solve 
      //the classic event race condition. I rewired RaiseEvent and 
      //EventMarshal to increase race condition tendency, and began 
      //looping only iterating between 20 and 200 times I was able to 
      //observe the race condition every time, with this lock in place, 
      //I have iterated 10's of thousands of times without failure. 
      lock (someEventLock) 
       somethingHappened.RaiseEvent(MyEventSender, e); 
      Thread.Sleep(1); //Optional, may make things more fluid. 
     } 

     /// <summary> 
     /// The Event Marshal. 
     /// </summary> 
     public void EventMarshal(IMyEventActions sender, MyEventArgs e) 
     { 
       if (sender.Event.InvokeRequired) 
        //Without the lock in Fire() a race condition would occur 
        //here when one thread closes the MyEvent form and another 
        //tries to Invoke it. 
        sender.Event.BeginInvoke(
         new OnSomethingHappenedDel(sender.OnSomethingHappened), 
         new object[] { e }); 
       else 
        sender.OnSomethingHappened(e); 
      if (SiblingEvents.Count > 0) Recurs(e); 
     } 

     /// <summary> 
     /// Provides safe recursion and event propagation through siblings. 
     /// </summary> 
     public void Recurs(MyEventArgs e) 
     { 
      e.Event.Add(this); 
      foreach (MyEvent m in SiblingEvents) 
       lock (m.someEventLock) //Prevents Race with UnSubscribeMeTo() 
        if (!e.Event.Contains(m)) //Provides safety from Eternals 
         m.Fire(e); 
     } 

     /// <summary> 
     /// Adds sibling MyEvent classes which to fire synchronously. 
     /// </summary> 
     public void SubscribeMeTo(MyEvent m) 
     { 
      if (this != m) SiblingEvents.Add(m); 
     } 

     /// <summary> 
     /// Removes sibling MyEvent's. 
     /// </summary> 
     public void UnSubscribeMeTo(MyEvent m) 
     { 
      lock (m.someEventLock) //Prevents race condition with Recurs() 
       if (SiblingEvents.Contains(m)) SiblingEvents.Remove(m); 
     } 

     protected override void OnFormClosing(FormClosingEventArgs e) 
     { 
      SomethingHappened -= somethingHappened; 
      base.OnFormClosing(e); 
     } 

     /// <summary> 
     /// Delegate backing the SomethingHappened event. 
     /// </summary> 
     private EventMarshalDel somethingHappened; 

     /// <summary> 
     /// A list of siblings to Eventcast. 
     /// </summary> 
     private List<MyEvent> SiblingEvents = new List<MyEvent>(); 
    } 

    /// <summary> 
    /// The interface used by MyThread to enlist OnSomethingHappened arbiter. 
    /// </summary> 
    public interface IMyEventActions 
    { 
     void OnSomethingHappened(MyEventArgs e); 
     MyEvent Event { get; set; } 
    } 

    public enum MyEventArgsFuncs : int 
    { 
     Shutdown = 0, 
     SomeOtherEvent, 
     TheLastEvent 
    }; 

    /// <summary> 
    /// Uses a string-referable enum to target functions handled 
    /// by OnSomethingHappened. 
    /// </summary> 
    public class MyEventArgs : EventArgs 
    { 
     public int Function { get; set; } 
     public List<MyEvent> Event = new List<MyEvent>(); 
     public MyEventArgs(string s) 
     { 
      this.Function = (int)Enum.Parse(typeof(MyEventArgsFuncs), s); 
     } 
    } 

    /// <summary> 
    /// This is a form with 3 buttons and a trackbar on it. 
    /// </summary> 
    /// <param name="m">An array of MyThread objects.</param> 
    // Create a designer form with 3 buttons and a trackbar and overwrite it 
    // with this, then hook up the buttons to button<1/2/3>_OnClick. 
    public partial class TestEvent : Form 
    { 
     public TestEvent() 
     { 
      InitializeComponent(); 
     } 

     public TestEvent(MyThread[] t) 
      : this() 
     { 
      myThreads = t; 
     } 

     /// <summary> 
     /// This button will fire a test event, which will write to the 
     /// console via OnSomethingHappened in another thread. 
     /// </summary> 
     private void button1_OnClick(object sender, EventArgs e) 
     { 
      Console.WriteLine("Firing SomeOtherEvent from Thread#" + Thread.CurrentThread.ManagedThreadId + " (Main)"); 
      myThreads[TrackbarVal].Event.Fire(new MyEventArgs("SomeOtherEvent")); 
     } 

     /// <summary> 
     /// This button will fire an event, which remotely shut down the 
     /// myEvent form and kill the thread. 
     /// </summary> 
     private void button2_OnClick(object sender, EventArgs e) 
     { 
      Console.WriteLine("Firing Shutdown event from Thread#" + Thread.CurrentThread.ManagedThreadId + " (Main)"); 
      myThreads[TrackbarVal].Event.Fire(new MyEventArgs("Shutdown")); 
     } 

     /// <summary> 
     /// This button will fire TheLastEvent, which will write to the 
     /// console via OnSomethingHappened in another thread. 
     /// </summary> 
     private void button3_OnClick(object sender, System.EventArgs e) 
     { 
      Console.WriteLine("Firing TheLastEvent from Thread#" + Thread.CurrentThread.ManagedThreadId + " (Main)"); 
      myThreads[TrackbarVal].Event.Fire(new MyEventArgs("TheLastEvent")); 
     } 

     public int TrackbarVal 
     { 
      get { return this.trackBar1.Value; } 
      set { this.trackBar1.Maximum = value; } 
     } 

     private MyThread[] myThreads; 
    } 

    /// <summary> 
    /// Stores Win32 API's. 
    /// </summary> 
    public class Win32 
    { 
     /// <summary> 
     /// Used to determine if there are messages waiting 
     /// </summary> 
     [System.Security.SuppressUnmanagedCodeSecurity] 
     [return: MarshalAs(UnmanagedType.Bool)] 
     [DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
     public static extern bool PeekMessage(out NativeMessage message, IntPtr handle, uint filterMin, uint filterMax, uint flags); 

     [StructLayout(LayoutKind.Sequential)] 
     public struct NativeMessage 
     { 
      public IntPtr handle; 
      public uint msg; 
      public IntPtr wParam; 
      public IntPtr lParam; 
      public uint time; 
      public System.Drawing.Point p; 
     } 
    } 
} 

namespace Extensions 
{ 
    using System; 
    using TestingEventsApplication; 

    /// <summary> 
    /// An extension method to null test for any OnSomethingHappened 
    /// event handlers. 
    /// </summary> 
    public static class Extension 
    { 
     public static void RaiseEvent(this EventMarshalDel @event, IMyEventActions sender, MyEventArgs e) 
     { 
      if (@event != null) 
       @event(sender, e); 
     } 
    } 
}