C # События между потоками выполняются в своем собственном потоке (Как)? - PullRequest
17 голосов
/ 08 марта 2010

Я хотел бы иметь две темы. Давайте назовем их:

  • Тема A
  • Резьба B

Поток A запускает событие, а поток B прослушивает это событие. Когда прослушиватель событий потока B выполняется, он выполняется с идентификатором потока потока A, поэтому я предполагаю, что он выполняется в потоке A.

То, что я хотел бы сделать, это иметь возможность передавать событие в поток B, говоря что-то вроде: «эй, данные готовы для вас, вы можете иметь дело с ними сейчас». Это событие должно быть выполнено в его собственном потоке, потому что он использует вещи, к которым имеет доступ только он (например, элементы управления пользовательского интерфейса).

Как я могу это сделать?

Спасибо за помощь.

Ответы [ 4 ]

12 голосов
/ 08 марта 2010

Вам нужно собрать информацию обратно в поток пользовательского интерфейса.

Обычно вы обрабатываете это в своем обработчике событий. Например, скажем, Поток A был вашим потоком пользовательского интерфейса - когда он подписался на событие в объекте в потоке B, обработчик событий будет запускаться внутри потока B. Однако он может просто перенаправить его обратно в поток пользовательского интерфейса: *

// 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...
            }));
}
8 голосов
/ 08 марта 2010

Возможно, проще всего подписаться, используя обработчик событий, который просто перенаправляет «настоящий» вызов обработчика в поток B. Например, обработчик может вызвать Control.BeginInvoke, чтобы выполнить некоторую работу в потоке B:

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

...

private void UpdateTextBox()
{
    // Do your real work here
}
3 голосов
/ 10 марта 2011

Я использую C # только несколько недель, но я столкнулся с тем же вопросом о том, как запускать события между потоками. Существует не так много полных примеров (любой?), И было трудно понять все отдельные работы разных экспертов. Через некоторое время я наконец-то разработал что-то, что работает, поэтому я хотел бы поделиться полным примером этого в этой теме для всех новичков, как я. Также я приветствую любые советы экспертов или критику, так как я довольно новичок в C #, и, безусловно, это можно улучшить.

Это полный пример, за исключением небольшой формы с 3 кнопками и вертикальной панелью, все с именами по умолчанию. Создайте форму в Designer и замените ее имеющимся у меня классом TestEvent, затем подключите 3 события OnClick для кнопок. Трекбар может использоваться для выбора потока, в который вы хотите запустить событие с помощью кнопок, и автоматически масштабируется при изменении numThreads в void Main (). 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);
        }
    }
}
3 голосов
/ 08 марта 2010

Если вы используете Windows Forms или WPF и не имеете ссылки Control для своих обработчиков событий, вы можете также захватить ссылку System.Threading.SynchronizationContext.Current в чем-то, работающем на поток пользовательского интерфейса и предоставьте эту ссылку вашим обработчикам событий.

Затем, когда вам нужно что-то запустить в потоке пользовательского интерфейса, вызовите Post () или Send () для захваченной SynchronizationContext ссылки из вашего обработчик событий, в зависимости от того, хотите ли вы запускать его асинхронно или синхронно.

По сути, это просто сахар для захвата ссылки Control и вызова Invoke () для него, но может упростить ваш код.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...