2009-12-16 1 views
31

Я пишу программу 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())?

+0

Какой поток вы хотите 'OperationFinished' событие будет поднят на? Это не может быть вашим вторичным потоком, так как вам явно не нужно блокировать его. Должен ли он быть основным потоком, или вы в порядке, когда он поднимается на другой поток, недавно созданный только для асинхронного обратного вызова? –

ответ

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!"); 
     } 
    } 
} 
+2

Почему бы просто не вызвать делегат многоадресной передачи напрямую, а не использовать GetInvocationList? – thecoop

+1

Как бы вы назвали слушателей событий асинхронно, только используя это? Конечно, вы могли бы называть * всех * слушателей в отдельном одном потоке - мое решение действительно доводит до уровня вызова * каждого слушателя в своем потоке - так что я мог видеть, что это слишком много. – STW

+0

То, как я изначально написал мой, если не было способа обработать событие в клиентском приложении (без прослушивателей), клиентское приложение выкинет исключение. Вы предотвращаете это, используя цикл for, который проходит через eventListeners? – PICyourBrain

0

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

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

+1

Не знаете, как BackgroundWorker помогает в этой ситуации. Конечно, это отличный вариант для того, чтобы выталкивать работу в отдельный поток, когда вам нужны уведомления, но в этом случае просто простой рабочий элемент, чтобы переместить обработчик в отдельный поток ... –

+0

Если я писал клиентское приложение, я может иметь метод, который обновляет графический интерфейс пользователя в workworker, и это остановит вызов функции OperationFinished() от блокировки, но поскольку я не пишу клиентское приложение, я не могу этого сделать. Вы говорите, что мой призыв к OpeartionFinished() должен быть в фоновой работе? – PICyourBrain

11

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

No. Как правило, вы могли бы назвать его как:

OperationFinished(this, EventArgs.Empty); 

Вы всегда должны передать объект в качестве отправителя - это ожидается в шаблоне (хотя обычно игнорируется). EventArgs.Empty лучше, чем новый EventArgs().

Для того, чтобы стрелять это в отдельном потоке, самый простой вариант, вероятно, просто использовать пул потоков:

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

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

+2

Сегодня я использую 'Task.Run' вместо пула потоков. – beruic

+1

@beruic Согласен. Это было написано в 2009 году;) –

6

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

0

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

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

Можете ли вы привести источники для резервного копирования, что 'EndInvoke()' не требуется? Мое понимание заключается в том, что всегда хорошая практика, чтобы гарантировать, что он называется, поскольку ресурсы потоковой передачи не обязательно освобождаются без вызова при определенных обстоятельствах. Кроме того, есть ли причина, по которой вы решили использовать ThreadStart, а не (относительно) исполнительный ThreadPool? И, наконец, это решение обрабатывает обновление пользовательского интерфейса, но я не думаю, что вопрос OP был ограничен этим - он не решает более широкую проблему повышения событий асинхронно. – STW

+1

Jon Skeet сказал, что это лучше всего: http://stackoverflow.com/questions/229554/whats-the-difference-between-invoke-and-begininvoke: «Обратите внимание, что команда Windows Forms гарантировала, что вы можете использовать Control.BeginInvoke в режиме «огонь и забухание», т. е. без вызова EndInvoke. Это не относится к асинхронным вызовам вообще: обычно каждый BeginXXX должен иметь соответствующий вызов EndXXX, как правило, в обратном вызове ». Также обратите внимание, что по крайней мере с WPF не существует метода Dispatcher.EndInvoke. –

+0

Я сделал свое решение обновлением пользовательского интерфейса, потому что это то, что ОП указывает: «Когда событие OperationFinished запускается, я хочу, чтобы клиентское приложение могло выполнять все, что ему нужно (например, обновлять графический интерфейс), не отказываясь от операции API». –

8

С Task Parallel Library теперь можно сделать следующее:

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

Отлично работает, спасибо за напоминание о методе «FromAsync» от TPL! – NumberFour

+0

Умный, но не будет работать для многоадресных делегатов –

+1

@FactorMytic Знаете ли вы, где я мог бы узнать больше о том, почему в этом случае это не работает? – piedar

3

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

}

 Смежные вопросы

  • Нет связанных вопросов^_^