How do I make an eventhandler run asynchronously?

前端 未结 7 653
迷失自我
迷失自我 2020-12-04 11:15

I am writing a Visual C# program that executes a continuous loop of operations on a secondary thread. Occasionally when that thread finishes a task I want it to trigger an e

相关标签:
7条回答
  • 2020-12-04 11:45

    Maybe Method2 or Method3 below can help :)

    public partial class Form1 : Form
    {
        private Thread SecondaryThread;
    
        public Form1()
        {
            InitializeComponent();
    
            OperationFinished += callback1;
            OperationFinished += callback2;
            OperationFinished += callback3;
        }
    
        private void Form1_Load(object sender, EventArgs e)
        {
            SecondaryThread = new Thread(new ThreadStart(SecondaryThreadMethod));
            SecondaryThread.Start();
        }
    
         private void SecondaryThreadMethod()
         {
            Stopwatch sw = new Stopwatch();
            sw.Restart();
    
            OnOperationFinished(new MessageEventArg("test1"));
            OnOperationFinished(new MessageEventArg("test2"));
            OnOperationFinished(new MessageEventArg("test3"));
            //This is where the program waits for whatever operations take
                 //place when OperationFinished is triggered.
    
            sw.Stop();
    
            Invoke((MethodInvoker)delegate
            {
                richTextBox1.Text += "Time taken (ms): " + sw.ElapsedMilliseconds + "\n";
            });
         }
    
        void callback1(object sender, MessageEventArg e)
        {
            Thread.Sleep(2000);
            Invoke((MethodInvoker)delegate
            {
                richTextBox1.Text += e.Message + "\n";
            });
        }
        void callback2(object sender, MessageEventArg e)
        {
            Thread.Sleep(2000);
            Invoke((MethodInvoker)delegate
            {
                richTextBox1.Text += e.Message + "\n";
            });
        }
    
        void callback3(object sender, MessageEventArg e)
        {
            Thread.Sleep(2000);
            Invoke((MethodInvoker)delegate
            {
                richTextBox1.Text += e.Message + "\n";
            });
        }
    
        public event EventHandler<MessageEventArg> OperationFinished;
    
        protected void OnOperationFinished(MessageEventArg e)
        {
            //##### Method1 - Event raised on the same thread ##### 
            //EventHandler<MessageEventArg> handler = OperationFinished;
    
            //if (handler != null)
            //{
            //    handler(this, e);
            //}
    
            //##### Method2 - Event raised on (the same) separate thread for all listener #####
            //EventHandler<MessageEventArg> handler = OperationFinished;
    
            //if (handler != null)
            //{
            //    Task.Factory.StartNew(() => handler(this, e));
            //}
    
            //##### Method3 - Event raised on different threads for each listener #####
            if (OperationFinished != null)
            {
                foreach (EventHandler<MessageEventArg> handler in OperationFinished.GetInvocationList())
                {
                    Task.Factory.FromAsync((asyncCallback, @object) => handler.BeginInvoke(this, e, asyncCallback, @object), handler.EndInvoke, null);
                }
            }
        }
    }
    
    public class MessageEventArg : EventArgs
    {
        public string Message { get; set; }
    
        public MessageEventArg(string message)
        {
            this.Message = message;
        }
    }
    

    }

    0 讨论(0)
  • 2020-12-04 11:47

    Try the BeginInvoke and EndInvoke methods on the event delegate - these return immediately, and allow you to use polling, a wait handle or a callback function to notify you when the method has completed. See here for an overview; in your example, the event is the delegate you'll be using

    0 讨论(0)
  • 2020-12-04 11:51

    I prefer to define a method that I pass to the child thread as a delegate which updates the UI. First define a delegate:

    public delegate void ChildCallBackDelegate();
    

    In the child thread define a delegate member:

    public ChildCallbackDelegate ChildCallback {get; set;}
    

    In the calling class define the method that updates the UI. You'll need to wrap it in the target control's dispatcher since its being called from a separate thread. Note the BeginInvoke. In this context EndInvoke isn't required:

    private void ChildThreadUpdater()
    {
      yourControl.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background
        , new System.Threading.ThreadStart(delegate
          {
            // update your control here
          }
        ));
    }
    

    Before you launch your child thread, set its ChildCallBack property:

    theChild.ChildCallBack = new ChildCallbackDelegate(ChildThreadUpdater);
    

    Then when the child thread wants to update the parent:

    ChildCallBack();
    
    0 讨论(0)
  • 2020-12-04 12:07

    Also, if I do not want to pass any parameters to the event handler is my syntax correct by using OperationFinished(null, new EventArgs()) ?

    No. Typically, you would call it as:

    OperationFinished(this, EventArgs.Empty);
    

    You should always pass an object as a sender - it's expected in the pattern (although typically ignored). EventArgs.Empty is better than new EventArgs(), as well.

    In order to fire this in a separate thread, the easiest option is probably to just use the thread pool:

    private void RaiseOperationFinished()
    {
           ThreadPool.QueueUserWorkItem( new WaitCallback( (s) =>
               {
                  if (this.OperationFinished != null)
                       this.OperationFinished(this, EventArgs.Empty);
               }));
    }
    

    That being said, raising an event on a separate thread is something that should be thoroughly documented, as it will potentially cause unexpected behavior.

    0 讨论(0)
  • 2020-12-04 12:07

    Look at the BackgroundWorker class. I think it does exactly what you are asking for.

    EDIT: What I think you are asking is how to fire an event when only a small part of the overall background task is complete. BackgroundWorker provides an event called "ProgressChanged" that allows you to report back to the main thread that some portion of the overall process is complete. Then, when all of the async work is complete, it raises the "RunWorkerCompleted" event.

    0 讨论(0)
  • 2020-12-04 12:08

    With the Task Parallel Library it is now possible to do the following:

    Task.Factory.FromAsync( ( asyncCallback, @object ) => this.OperationFinished.BeginInvoke( this, EventArgs.Empty, asyncCallback, @object ), this.OperationFinished.EndInvoke, null );
    
    0 讨论(0)
提交回复
热议问题