Bezdepbonusu7Программирование: статьи и ответы

Вопрос

Как заставить обработчик событий работать асинхронно?

Автор вопроса: PICyourBrainОтветов: 7

Я пишу программу на 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, я хочу, чтобы клиентское приложение могло делать все, что ему нужно (т. Е. Соответствующим образом обновлять графический интерфейс), не нарушая работу API.

Кроме того, если я не хочу передавать какие-либо параметры обработчику событий, правильный ли мой синтаксис с использованием OperationFinished(null, new EventArgs())?

  • В каком потоке вы хотите вызвать событие OperationFinished? Это не может быть ваш вторичный поток, поскольку вы явно не требуете его блокировать. Должен ли он быть основным потоком, или вас устраивает, что он поднимается в другом потоке, созданном только для целей асинхронного обратного вызова? Pavel Minaev 16.12.2009

multithreadingevent-handlingsynchronouseventargs

Ответы

Ответ 1 · рейтинг 62 · STW

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

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

Ответ 2 · рейтинг 17 · WhiteKnight

С помощью параллельной библиотеки задач теперь можно делать следующее:

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

Ответ 3 · рейтинг 12 · Reed Copsey

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

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

Ответ 4 · рейтинг 6 · thecoop

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

Ответ 5 · рейтинг 4 · Jiefeng Koh

Может быть, 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;
    }
}

}

Ответ 6 · Ed Power

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

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

Ответ 7 · Mark Ewer

Посмотрите на класс BackgroundWorker. Я думаю, он делает именно то, о чем вы просите.

РЕДАКТИРОВАТЬ: Я думаю, вы спрашиваете, как запустить событие, когда завершена только небольшая часть общей фоновой задачи. BackgroundWorker предоставляет событие под названием "ProgressChanged", которое позволяет вам сообщить основному потоку, что некоторая часть всего процесса завершена. Затем, когда вся асинхронная работа завершена, он вызывает событие "RunWorkerCompleted".

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы