【问题标题】:C# Events between threads executed in their own thread (How to)?在自己的线程中执行的线程之间的 C# 事件(如何)?
【发布时间】:2010-03-08 19:12:09
【问题描述】:

我想要两个线程。让我们称呼他们:

  • 线程 A
  • 线程 B

线程 A 触发一个事件,线程 B 监听这个事件。 当线程B事件监听器执行时,它是用线程A的线程ID执行的,所以我猜它是在线程A内执行的。

我想做的是能够向线程 B 触发事件,说如下:“嘿,数据已经为你准备好了,你现在可以处理它了”。这个事件必须在它自己的线程中执行,因为它使用了只有他才能访问的东西(比如 UI 控件)。

我该怎么做?

感谢您的帮助。

【问题讨论】:

    标签: c# events multithreading delegates


    【解决方案1】:

    您需要将信息编组回 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...
                }));
    }
    

    【讨论】:

    • 能否请您提供need to marshal the information back 的参考资料,因为我知道跨进程和公寓需要编组。如果这些线程在同一个进程中,为什么要编组? (并且没有公寓,因为没有使用 COM)。
    • 我在这里得到了答案here
    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

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

      然后,当您需要在 UI 线程上运行某些东西时,根据您希望它是异步运行还是同步运行,在从事件处理程序捕获的 SynchronizationContext 引用上调用 Post()Send()

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

      【讨论】:

        【解决方案4】:

        我现在只使用 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
            {
        
                /// <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);
                }
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2021-06-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-08-19
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多