C#在自己的线程中执行的线程之间的事件(如何)?

我想要两个主题。 我们打电话给他们:

  • 线程A.
  • 线程B

线程A触发事件,线程B侦听此事件。 执行线程B事件侦听器时,它是使用线程A的线程ID执行的,所以我猜它是在线程A中执行的。

我想做的是能够向线程B发起事件,例如:“嘿,数据已准备就绪,你现在可以处理它”。 此事件必须在其自己的Thread中执行,因为它使用只有他可以访问的内容(如UI控件)。

我怎样才能做到这一点 ?

谢谢你的帮助。

您需要将信息封送回UI线程。

通常,您将在事件处理程序中处理此问题。 例如,假设线程A是您的UI线程 – 当它在线程B中的对象上订阅事件时,事件处理程序将在线程B内运行。但是,它可以将其重新编组回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... })); } 

最简单的方法可能是使用一个事件处理程序进行订阅,该处理程序只将“真正的”处理程序调用封送到线程B上。例如,处理程序可以调用Control.BeginInvoke在线程B上做一些工作:

 MethodInvoker realAction = UpdateTextBox; foo.SomeEvent += (sender, args) => textBox.BeginInvoke(realAction); ... private void UpdateTextBox() { // Do your real work here } 

我现在只使用C#几个星期,但我遇到了如何跨线程触发事件的相同问题。 没有很多完整的例子(任何?),很难理解各个专家的所有个别作品。 经过一段时间我终于开发出了一些有用的东西,所以我想在这个主题上为任何像我这样的新手分享一个完整的例子。 我也欢迎任何专家建议或批评,因为我对C#很陌生,当然这可以改进。

这是一个完整的示例,减去了一个带有3个按钮和一个垂直轨迹栏的小表单,所有这些都带有默认名称。 在Designer中创建表单并使用我拥有的TestEvent类覆盖它,然后连接3个按钮OnClick事件。 轨迹栏可用于通过按钮选择要触发事件的线程,并在更改void Main()中的numThreads时自动缩放。 Button2将发送一个事件来关闭该线程。

MyEvent类可以与任何实现IMyEventActions接口的类一起使用。 使用MyEvent的类将自动接收OnSomethingHappened(…)中的触发事件。 此外,实例化MyEvent的类可以递归地订阅其他类事件。 通过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 { ///  /// The main entry point for the application. ///  [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. // eg 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); } } ///  /// Used to determine when a task is complete. ///  public class QuickSync { public bool Sync { get { lock (this) return sync; } set { lock (this) sync = value; } } private bool sync; } ///  /// A class representing the operating body of a Background thread. /// Inherits IMyEventActions. ///  /// a QuickSync boxed bool. public class MyThread : IMyEventActions { ///  /// An reference to the Thread object used by this thread. ///  public Thread thread { get; set; } ///  /// Tracks the MyEvent object used by the thread. ///  public MyEvent Event { get; set;} ///  /// Satisfies IMyEventActions and provides a method to implement /// Event actions ///  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; } } ///  /// The method used by a thread starting delegate. ///  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); } ///  /// The operating body of the thread. ///  private void OnApplicationIdle(object sender, EventArgs e) { while (this.AppStillIdle) { //Do your threads work here... Console.Write("."); Thread.Sleep(1000); } } ///  /// Monitors the Threads msg procedure to make sure we handle messages. ///  public bool AppStillIdle { get { Win32.NativeMessage msg; return !Win32.PeekMessage(out msg, IntPtr.Zero, 0, 0, 0); } } } ///  /// Houses all of the plumbing necessary to fire cross thread events. ///  public class MyEvent : System.Windows.Forms.Form { ///  /// A reference to the object using this MyEvent, used during recursion. ///  public IMyEventActions MyEventSender { get; set; } ///  /// Lock for somethingHappened delegate access. ///  public readonly object someEventLock = new object(); ///  /// Public access to the event SomethingHappened with a locking /// subscription mechanism for thread safety. ///  public event EventMarshalDel SomethingHappened { add { lock (someEventLock) somethingHappened += value; } remove { lock (someEventLock) //Contributes to preventing race condition somethingHappened -= value; } } ///  /// The trigger of MyEvent class. ///  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. } ///  /// The Event Marshal. ///  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); } ///  /// Provides safe recursion and event propagation through siblings. ///  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); } ///  /// Adds sibling MyEvent classes which to fire synchronously. ///  public void SubscribeMeTo(MyEvent m) { if (this != m) SiblingEvents.Add(m); } ///  /// Removes sibling MyEvent's. ///  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); } ///  /// Delegate backing the SomethingHappened event. ///  private EventMarshalDel somethingHappened; ///  /// A list of siblings to Eventcast. ///  private List SiblingEvents = new List(); } ///  /// The interface used by MyThread to enlist OnSomethingHappened arbiter. ///  public interface IMyEventActions { void OnSomethingHappened(MyEventArgs e); MyEvent Event { get; set; } } public enum MyEventArgsFuncs : int { Shutdown = 0, SomeOtherEvent, TheLastEvent }; ///  /// Uses a string-referable enum to target functions handled /// by OnSomethingHappened. ///  public class MyEventArgs : EventArgs { public int Function { get; set; } public List Event = new List(); public MyEventArgs(string s) { this.Function = (int)Enum.Parse(typeof(MyEventArgsFuncs), s); } } ///  /// This is a form with 3 buttons and a trackbar on it. ///  /// An array of MyThread objects. // 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; } ///  /// This button will fire a test event, which will write to the /// console via OnSomethingHappened in another thread. ///  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")); } ///  /// This button will fire an event, which remotely shut down the /// myEvent form and kill the thread. ///  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")); } ///  /// This button will fire TheLastEvent, which will write to the /// console via OnSomethingHappened in another thread. ///  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; } ///  /// Stores Win32 API's. ///  public class Win32 { ///  /// Used to determine if there are messages waiting ///  [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; ///  /// An extension method to null test for any OnSomethingHappened /// event handlers. ///  public static class Extension { public static void RaiseEvent(this EventMarshalDel @event, IMyEventActions sender, MyEventArgs e) { if (@event != null) @event(sender, e); } } } 

如果您正在使用Windows窗体或WPF,并且没有来自事件处理程序的Control引用,您还可以在UI线程上运行的某些内容中捕获System.Threading.SynchronizationContext.Current的引用,并将该引用公开给你的事件处理程序。

然后,当您需要在UI线程上运行某些内容时,从事件处理程序调用捕获的SynchronizationContext引用上的Post()或Send() ,具体取决于您是希望它是异步还是同步运行。

基本上这只是捕获Control引用并在其上调用Invoke()的糖,但可以使您的代码更简单。