如何使事件处理程序异步运行?

发布于 2024-08-15 21:19:44 字数 812 浏览 6 评论 0原文

我正在编写一个 Visual C# 程序,该程序在辅助线程上执行连续的操作循环。有时,当该线程完成任务时,我希望它触发事件处理程序。我的程序就是这样做的,但是当事件处理程序被触发时,辅助线程会等待事件处理程序完成,然后再继续线程。我该如何让它继续下去?这是我目前的结构方式...

class TestClass 
{
  private Thread SecondaryThread;
  public event EventHandler OperationFinished;

  public void StartMethod()
  {
    ...
    SecondaryThread.Start();      //start the secondary thread
  }

  private void SecondaryThreadMethod()
  {
    ...
    OperationFinished(null, new EventArgs());
    ...  //This is where the program waits for whatever operations take
         //place when OperationFinished is triggered.
  }

}

此代码是我的一台设备的 API 的一部分。当 OperationFinished 事件被触发时,我希望客户端应用程序能够执行所需的任何操作(即相应地更新 GUI),而无需拖拉 API 操作。

另外,如果我不想将任何参数传递给事件处理程序,使用 OperationFinished(null, new EventArgs()) 的语法是否正确?

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 eventhandler. My program does that but the when the event handler is triggered, the secondary thread waits until the event handler is finished before continuing the thread. How do I make it continue? Here is the way I currently have it structured...

class TestClass 
{
  private Thread SecondaryThread;
  public event EventHandler OperationFinished;

  public void StartMethod()
  {
    ...
    SecondaryThread.Start();      //start the secondary thread
  }

  private void SecondaryThreadMethod()
  {
    ...
    OperationFinished(null, new EventArgs());
    ...  //This is where the program waits for whatever operations take
         //place when OperationFinished is triggered.
  }

}

This code is part of an API for one of my devices. When the OperationFinished event is triggered I want the client application to be able to do whatever it needs to (i.e. update the GUI accordingly) without haulting the API operation.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

雨的味道风的声音 2024-08-22 21:19:44

那么您想以防止侦听器阻塞后台线程的方式引发事件吗?给我几分钟时间来举一个例子;非常简单 :-)

我们开始吧:首先是一个重要的说明!每当您调用 BeginInvoke 时,您都必须调用相应的 EndInvoke,否则如果调用的方法抛出异常返回一个值,那么ThreadPool线程将永远不会被释放回池中,从而导致线程泄漏!

class TestHarness
{

    static void Main(string[] args)
    {
        var raiser = new SomeClass();

        // Emulate some event listeners
        raiser.SomeEvent += (sender, e) => { Console.WriteLine("   Received event"); };
        raiser.SomeEvent += (sender, e) =>
        {
            // Bad listener!
            Console.WriteLine("   Blocking event");
            System.Threading.Thread.Sleep(5000);
            Console.WriteLine("   Finished blocking event");
        };

        // Listener who throws an exception
        raiser.SomeEvent += (sender, e) =>
        {
            Console.WriteLine("   Received event, time to die!");
            throw new Exception();
        };

        // Raise the event, see the effects
        raiser.DoSomething();

        Console.ReadLine();
    }
}

class SomeClass
{
    public event EventHandler SomeEvent;

    public void DoSomething()
    {
        OnSomeEvent();
    }

    private void OnSomeEvent()
    {
        if (SomeEvent != null)
        {
            var eventListeners = SomeEvent.GetInvocationList();

            Console.WriteLine("Raising Event");
            for (int index = 0; index < eventListeners.Count(); index++)
            {
                var methodToInvoke = (EventHandler)eventListeners[index];
                methodToInvoke.BeginInvoke(this, EventArgs.Empty, EndAsyncEvent, null);
            }
            Console.WriteLine("Done Raising Event");
        }
    }

    private void EndAsyncEvent(IAsyncResult iar)
    {
        var ar = (System.Runtime.Remoting.Messaging.AsyncResult)iar;
        var invokedMethod = (EventHandler)ar.AsyncDelegate;

        try
        {
            invokedMethod.EndInvoke(iar);
        }
        catch
        {
            // Handle any exceptions that were thrown by the invoked method
            Console.WriteLine("An event listener went kaboom!");
        }
    }
}

So you want to raise the event in a manner that prevents the listeners from blocking the background thread? Gimme a couple minutes to whip up an example; it's pretty simple :-)

Here we go: first an important note! Whenever you call BeginInvoke you must call the corresponding EndInvoke, otherwise if the invoked method threw an exception or returned a value then the ThreadPool thread will never be released back to the pool, resulting in a thread-leak!

class TestHarness
{

    static void Main(string[] args)
    {
        var raiser = new SomeClass();

        // Emulate some event listeners
        raiser.SomeEvent += (sender, e) => { Console.WriteLine("   Received event"); };
        raiser.SomeEvent += (sender, e) =>
        {
            // Bad listener!
            Console.WriteLine("   Blocking event");
            System.Threading.Thread.Sleep(5000);
            Console.WriteLine("   Finished blocking event");
        };

        // Listener who throws an exception
        raiser.SomeEvent += (sender, e) =>
        {
            Console.WriteLine("   Received event, time to die!");
            throw new Exception();
        };

        // Raise the event, see the effects
        raiser.DoSomething();

        Console.ReadLine();
    }
}

class SomeClass
{
    public event EventHandler SomeEvent;

    public void DoSomething()
    {
        OnSomeEvent();
    }

    private void OnSomeEvent()
    {
        if (SomeEvent != null)
        {
            var eventListeners = SomeEvent.GetInvocationList();

            Console.WriteLine("Raising Event");
            for (int index = 0; index < eventListeners.Count(); index++)
            {
                var methodToInvoke = (EventHandler)eventListeners[index];
                methodToInvoke.BeginInvoke(this, EventArgs.Empty, EndAsyncEvent, null);
            }
            Console.WriteLine("Done Raising Event");
        }
    }

    private void EndAsyncEvent(IAsyncResult iar)
    {
        var ar = (System.Runtime.Remoting.Messaging.AsyncResult)iar;
        var invokedMethod = (EventHandler)ar.AsyncDelegate;

        try
        {
            invokedMethod.EndInvoke(iar);
        }
        catch
        {
            // Handle any exceptions that were thrown by the invoked method
            Console.WriteLine("An event listener went kaboom!");
        }
    }
}
零度℉ 2024-08-22 21:19:44

使用任务并行库,现在可以执行以下操作:

Task.Factory.FromAsync( ( asyncCallback, @object ) => this.OperationFinished.BeginInvoke( this, EventArgs.Empty, asyncCallback, @object ), this.OperationFinished.EndInvoke, null );

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 );
已下线请稍等 2024-08-22 21:19:44

此外,如果我不想将任何参数传递给事件处理程序,那么使用 OperationFinished(null, new EventArgs()) 的语法是否正确?

不。通常,您将其称为:

OperationFinished(this, EventArgs.Empty);

您应该始终将对象作为发送者传递 - 这是模式中所期望的(尽管通常会被忽略)。 EventArgs.Empty 也比 new EventArgs() 更好。

为了在单独的线程中触发此事件,最简单的选择可能是仅使用线程池:

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

也就是说,在单独的线程上引发事件应该被彻底记录,因为它可能会导致意外的行为。

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.

病毒体 2024-08-22 21:19:44

尝试事件委托上的 BeginInvoke 和 EndInvoke 方法 - 这些方法立即返回,并允许您使用轮询、等待句柄或回调函数在方法完成时通知您。请参阅此处了解概述;在您的示例中,事件是您将使用的委托

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

埋葬我深情 2024-08-22 21:19:44

也许下面的方法2或方法3可以帮助:)

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;
    }
}

}

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;
    }
}

}

鲜肉鲜肉永远不皱 2024-08-22 21:19:44

我更喜欢定义一个方法,将其作为更新 UI 的委托传递给子线程。首先定义一个委托:

public delegate void ChildCallBackDelegate();

在子线程中定义一个委托成员:

public ChildCallbackDelegate ChildCallback {get; set;}

在调用类中定义更新UI的方法。您需要将其包装在目标控件的调度程序中,因为它是从单独的线程调用的。请注意 BeginInvoke。在这种情况下,不需要 EndInvoke:

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

在启动子线程之前,设置其 ChildCallBack 属性:

theChild.ChildCallBack = new ChildCallbackDelegate(ChildThreadUpdater);

然后当子线程想要更​​新父线程时:

ChildCallBack();

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();
帥小哥 2024-08-22 21:19:44

查看 BackgroundWorker 类。我认为它完全符合您的要求。

编辑:
我认为您要问的是当整个后台任务仅完成一小部分时如何触发事件。 BackgroundWorker 提供了一个名为“ProgressChanged”的事件,允许您向主线程报告整个过程的某些部分已完成。然后,当所有异步工作完成时,它会引发“RunWorkerCompleted”事件。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文