2010-03-08 4 views
15

Ich hätte gerne zwei Threads. Nennen wir sie:C# Ereignisse zwischen Threads, die in einem eigenen Thread ausgeführt werden (Vorgehensweise)?

  • Thread A
  • Thema B

Thread A feuert ein Ereignis und Thread B zu dieser Veranstaltung hören. Wenn der Thread-B-Event-Listener ausgeführt wird, wird er mit der Thread-ID des Threads A ausgeführt. Ich nehme an, dass er innerhalb des Threads A ausgeführt wird. Ich möchte Thread zu Thread B auslösen etwas sagen wie: "Hey, ein Daten ist bereit für Sie, Sie können jetzt damit umgehen". Dieses Ereignis muss in einem eigenen Thread ausgeführt werden, da es Dinge verwendet, auf die nur er zugreifen kann (wie UI-Steuerelemente).

Wie kann ich das tun?

Vielen Dank für Ihre Hilfe.

Antwort

11

Sie müssen die Informationen zurück in den UI-Thread marshallen.

Normalerweise würden Sie dies in Ihrem Event-Handler behandeln. Angenommen, Thread A war Ihr UI-Thread - wenn er ein Ereignis für ein Objekt in Thread B abonniert hat, wird der Event-Handler innerhalb von Thread B ausgeführt. Er kann dann jedoch einfach zurück in den UI-Thread marshalieren:

// 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

Könnten Sie bitte Referenzen für "müssen die Informationen zurückmarschieren", wie ich weiß Marshalling ist für Cross-Prozess und Wohnung erforderlich. Warum Marshal, wenn diese Threads im selben Prozess sind? (und keine Wohnung, weil COM nicht benutzt wird). – bjan

+0

Ich habe meine Antwort hier ** [hier] (http://stackoverflow.com/questions/1361033/what-does-stathread-do) ** – bjan

6

der einfachste Weg ist wahrscheinlich ein Event-Handler zu zeichnen verwendet, die nur die „echten“ Handler Aufruf B. auf das Gewinde Marschall zum Beispiel könnte der Handler Control.BeginInvoke rufen einige Arbeit auf Thread B zu tun:

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

... 

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

Wenn Sie Windows Forms oder WPF verwenden und keine Control-Referenz von Ihren Event-Handlern zur Hand haben, können Sie auch die Referenz erfassen e von System.Threading.SynchronizationContext.Current in etwas, das im UI-Thread ausgeführt wird, und diesen Verweis für Ihre Ereignishandler verfügbar machen.

Dann, wenn Sie etwas laufen auf dem UI-Thread haben müssen, rufen Sie Post() oder Send() auf dem aufgenommenen SynchronizationContext Referenz von Ihrem Event-Handler, je nachdem, ob Sie es wollen asynchron oder synchron ausgeführt werden.

Grundsätzlich ist dies nur Zucker um eine Control Referenz zu erfassen und Invoke() darauf anzurufen, kann aber Ihren Code einfacher machen.

3

Ich habe C# erst seit ein paar Wochen verwendet, aber ich habe die gleiche Frage, wie Ereignisse über Threads ausgelöst werden. Es gibt nicht viele vollständige Beispiele (irgendwelche?) Und es war schwierig, alle einzelnen Stücke von verschiedenen Experten zu verstehen. Nach einiger Zeit habe ich endlich etwas entwickelt, das funktioniert, also wollte ich ein komplettes Beispiel davon in diesem Thread für alle Neulinge wie mich teilen. Auch ich begrüße jeden Expertenrat oder Kritik, da ich noch ziemlich neu in C# bin und sicherlich kann dies verbessert werden.

Dies ist ein vollständiges Beispiel minus ein kleines Formular mit 3 Tasten und einer vertikalen Trackbar, alle mit Standardnamen. Machen Sie das Formular in Designer und überschreiben Sie es mit der TestEvent-Klasse, die ich habe, dann schließen Sie die 3 Schaltflächen OnClick Ereignisse. Die Trackbar kann verwendet werden, um den Thread auszuwählen, über den Sie ein Ereignis auslösen möchten, und wird automatisch skaliert, wenn Sie numThreads in void Main() ändern. Button2 sendet ein Ereignis zum Herunterfahren des Threads.

Die MyEvent-Klasse kann in Verbindung mit jeder Klasse verwendet werden, die die IMyEventActions-Schnittstelle implementiert. Die Klasse, die MyEvent verwendet, empfängt automatisch ausgelöste Ereignisse in OnSomethingHappened (...). Darüber hinaus kann die Klasse, die MyEvent instanziiert, andere Klassenereignisse rekursiv abonnieren. Das Abfeuern von Ereignissen wird einfach durch die MyEvent.Fire (...) Methode erreicht.

// 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); 
     } 
    } 
} 
Verwandte Themen