我正在编写一个 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()) ?

有帮助吗?

解决方案

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

开始了: 首先是一个重要的说明! 每当你打电话时 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!");
        }
    }
}

其他提示

随着 任务并行库 现在可以执行以下操作:

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

另外,如果我不想将任何参数传递给事件处理程序,使用 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);
           }));
}

话虽如此,在单独的线程上引发事件应该被彻底记录下来,因为它可能会导致意外的行为。

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

也许下面的方法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;
    }
}

}

我更喜欢定义一个方法,将其作为更新 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();

看着那(这 后台工作者 班级。我认为它完全符合您的要求。

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top