Как заставить eventhandler работать асинхронно?

Я пишу программу Visual C#, которая выполняет непрерывный цикл операций над вторичным потоком. Иногда, когда этот поток завершает задачу, я хочу, чтобы он запускал eventhandler. Моя программа делает это, но когда запускается обработчик событий, вторичный поток ждет, пока обработчик событий не будет завершен, прежде чем продолжить поток. Как сделать так, чтобы это продолжалось? Вот как я сейчас это структурировал...

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()) ?

7 ответов


Итак, вы хотите поднять событие таким образом, чтобы слушатели не блокировали фоновый поток? Дайте мне пару минут, чтобы на скорую руку пример; это довольно просто: -)

вот так:сначала важное примечание! всякий раз, когда вы называете 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!");
        }
    }
}

кроме того, если я не хочу передавать какие-либо параметры обработчику событий, является ли мой синтаксис правильным с помощью 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);
           }));
}

это, как говорится, поднятие события в отдельном потоке-это то, что должно быть тщательно документировано, поскольку это потенциально вызовет неожиданное поведение.


С Параллельных Задач Библиотека Теперь можно сделать следующее:

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

попробуйте методы BeginInvoke и EndInvoke на делегате события - они возвращаются немедленно и позволяют использовать опрос, дескриптор ожидания или функцию обратного вызова, чтобы уведомить вас, когда метод завершен. См.здесь для обзора; в вашем примере событие является делегатом, который вы будете использовать


возможно, Method2 или Method3 ниже могут помочь:)

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

}


Я предпочитаю определять метод, который я передаю дочернему потоку как делегат, который обновляет пользовательский интерфейс. Сначала определите делегат:

public delegate void ChildCallBackDelegate();

в дочернем потоке определите член делегата:

public ChildCallbackDelegate ChildCallback {get; set;}

в вызывающем классе определите метод, который обновляет пользовательский интерфейс. Вам нужно будет обернуть его в диспетчер целевого элемента управления, так как он вызывается из отдельного потока. Обратите внимание на 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 класса. Я думаю, он делает именно то, о чем вы просите.

изменить: Я думаю, вы спрашиваете, как запустить событие, когда только небольшая часть общей фоновой задачи завершена. BackgroundWorker предоставляет событие под названием "ProgressChanged", которое позволяет вам сообщить главному потоку, что некоторая часть общего процесса завершена. Затем, когда вся асинхронная работа завершена, она вызывает "Событие RunWorkerCompleted".