Как ждать сигнала в WinForms, одновременно прослушивая события?
Случай 1
Вот моя установка.
internal class MyClass
{
private ApiObject apiObject;
private bool cond1;
private bool cond2;
internal MyClass()
{
this.apiObject = new ApiObject();
this.apiObject.ApiStateUpdate += new ApiStateUpdateEventHandler(ApiStateHandler);
//wait for both conditions to be true
}
private void ApiStateHandler(string who, int howMuch)
{
if(who.Equals("Something") && howMuch == 1)
this.cond1 = true;
else if(who.Equals("SomethingElse") && howMuch == 1)
this.cond2 = true;
}
}
Как я могу ждать, пока оба условия будут истинными?
Если я это сделаю:
while(!(this.cond1 && this.cond2))
{
System.Threading.Thread.Sleep(1000);
}
Код в ApiStateHandler()
, кажется, никогда не выполняется.
Если я это сделаю:
while(!(this.cond1 && this.cond2))
{
System.Windows.Forms.Application.DoEvents();
}
Это работает, но кажется пустой тратой ресурсов и халтурой.
В основном я думаю, что мне нужен способ wait
, но без блокировки потока. Как правильно это делать?
Кейс 2
Второй случай несколько похож (и связан) и иллюстрирует ту же проблему.internal class MyClass
{
private ApiNotifyClass apiNotifyClass;
private shouldContinue = false;
internal MyClass()
{
//in addition to the code from above
this.apiNotifyClass = new ApiNotifyClass();
this.apiNotifyClass.ApiFound += ApiNofityFoundEventHandler(ApiNotifyHandler);
}
internal void Send(SomethingToSend somethigToSend)
{
Verifyer verifier = this.apiObject.ApiGet(somethingToSend);
this.apiNotifyClass.ApiAttach(verifier);
//wait for the shouldContinue to be true
this.apiObject.ApiSend(verifier);
this.apiNotifyClass.ApiDetach(verifier);
}
private void ApiNotifyHandler()
{
this.shouldContinue = true;
}
}
При вызове Send()
будет создан объект Verifier
, и метод должен дождаться выполнения ApiNotifyHandler
(т. е. события ApiFound
) перед вызовом ApiSend()
.
Извините за очень длинный вопрос, но я подумал о дать как можно больше информации, чтобы помочь вам помочь мне.
[обновление ]
Я вынужден использовать .Net 2.0.
3 ответа:
Лучший способ справиться с этим-перефакторировать код, чтобы использовать
async/await
и превратитьApiStateUpdate
событие в ожидаемую задачу сTaskCompletionSource
(шаблон ВП ).Если вы действительно хотите синхронно ждать события в потоке пользовательского интерфейса, посмотрите на
WaitWithDoEvents
из здесь илиCoWaitForMultipleHandles
из здесь, они делают именно это. Имейте в виду, что этот подход создает вложенный модальный цикл сообщений, причем наиболее заметным следствием является возможное повторное включение кода (обсуждается подробно здесь ).[отредактировано] то, что вы пытаетесь сделать здесь,-это асинхронно-синхронный мост, который почти всегда сам по себе плохая идея. Более того, я только что понял, что вы делаете это в конструкторе. Конструкторы, по своей природе, не должны иметь никакого асинхронного кода внутри, они атомарны. Всегда есть лучший способ учесть длительную процедуру инициализации из конструктора. @StephenCleary рассказывает об этом в своем очень информативном блоге сообщение .
Относительно ограничений .NET 2.0. Хотя
async/await
может быть революционной концепцией, Концепция государственной машины, стоящая за ней, не является чем-то новым. Вы всегда можете смоделировать его с помощью цепочки обратных вызовов делегатов и событий. Анонимные делегаты существуют с .NET 2.0. Например, ваш код может выглядеть следующим образом:internal class MyClass { private ApiObject apiObject; public event EventHandler Initialized; internal MyClass() { this.apiObject = new ApiObject(); } public void Initialize() { ApiStateUpdateEventHandler handler = null; handler = delegate(string who, int howMuch) { bool cond1 = false; bool cond2 = false; if(who.Equals("Something") && howMuch == 1) cond1 = true; else if(who.Equals("SomethingElse") && howMuch == 1) cond2 = true; //wait for both conditions to be true if ( !cond1 && !cond2 ) return; this.apiObject.ApiStateUpdate -= handler; // fire an event when both conditions are met if (this.Initialized != null) this.Initialized(this, new EventArgs()); }; this.apiObject.ApiStateUpdate += handler; } }
Клиентский код, использующий
MyClass
, может выглядеть следующим образом:MyClass myObject = new MyClass(); myObject.Initialized += delegate { MessageBox.Show("Hello!"); }; myObject.Initialize();
Выше было бы правильным асинхронным шаблоном на основе событий для .NET 2.0 . Более простым, но худшим решением было бы реализовать асинхронно-синхронный мост, используя
WaitWithDoEvents
(из здесь, основанный наMsgWaitForMultipleObjects
), который может выглядеть следующим образом:internal class MyClass { private ApiObject apiObject; internal MyClass() { this.apiObject = new ApiObject(); Initialize(); } private void Initialize() { using (ManualResetEvent syncEvent = new ManualResetEvent()) { ApiStateUpdateEventHandler handler = null; handler = delegate(string who, int howMuch) { bool cond1 = false; bool cond2 = false; if(who.Equals("Something") && howMuch == 1) cond1 = true; else if(who.Equals("SomethingElse") && howMuch == 1) cond2 = true; //wait for both conditions to be true if ( !cond1 && !cond2 ) return; this.apiObject.ApiStateUpdate -= handler; syncEvent.Set(); }; this.apiObject.ApiStateUpdate += handler; WaitWithDoEvents(syncEvent, Timeout.Infinite); } } }
Однако это было бы еще более эффективно, чем цикл напряженного ожидания от вашего вопроса:
while(!(this.cond1 && this.cond2)) { System.Windows.Forms.Application.DoEvents(); }
Вам придется выполнять блокирующий код асинхронно. В противном случае вы бы повесили поток пользовательского интерфейса, который не является хорошим. Существуют различные способы сделать это. Вот тот, который использует новые ключевые слова
async
иawait
. Я признаю сразу, что это много, чтобы проглотить и возможно только в .NET 4.5+.Относительно Случая #1
Во-первых, преобразовать (на основе событий ВП асинхронный скороговорка) в кран (задач на основе асинхронной модели). Этот взгляд некрасиво, потому что с ВП трудно иметь дело. Вы должны подписаться на событие, а затем отказаться от подписки, когда вы закончите с ним.
private Task<ApiObject> CreateApiObjectAsync() { bool cond1 = false; bool cond2 = false; var tcs = new TaskCompletionSource<ApiObject>(); ApiObject instance = null; ApiStateEventHandler handler = null; handler = (who, howmuch) => { cond1 = cond1 || (who == "Something" && howmuch == 1); cond2 = cond2 || (who == "SomethingElse" && howmuch == 1); if (cond1 && cond2) { instance.ApiStateUpdate -= handler; tcs.SetResult(instance); } } var instance = new ApiObject(); instance.ApiStateUpdate += handler; return tcs.Task; }
Как только вы это сделаете, он будет использоваться вот так.
internal class MyClass { private ApiObject apiObject; internal MyClass() { InitializeAsync(); } private async Task InitializeAsync() { apiObject = await CreateApiObjectAsync(); // At this point the instance is created and fully initialized. } }
Я рекомендую вам прочитать блог Стивена Клири на асинхронной инициализации с использованием
async
иawait
, прежде чем вы это сделаете. На самом деле, прочитайте всю его асинхронную серию ООП. Это действительно хорошо.Относительно Случая #2
Во многих отношениях с этим случаем легче иметь дело, потому что конструкторы и инициализация объекта не вступают в игру. Однако вам все равно придется использовать ту же стратегию, что и выше. Во-первых, преобразуйте стиль API EAP в стиль TAP. Если это условие ожидания не зависит от события из
ApiObject
, то просто выберите любую логику ожидания, которая вам нужна в методе TAP, и периодически оценивайте ее. Желательно, чтобы это было сделано без напряженного ожидания. Затемawait
Метод TAP ты создал. Однако не забудьте отметитьSend
какasync
при этом.