В моем приложении C # / XAML metro есть кнопка, которая запускает длительный процесс. Итак, как рекомендовано, я использую async / await, чтобы убедиться, что поток пользовательского интерфейса не заблокирован:
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
await GetResults();
}
private async Task GetResults()
{
// Do lot of complex stuff that takes a long time
// (e.g. contact some web services)
...
}
Иногда для того, чтобы происходить внутри GetResults, потребуется дополнительный пользовательский ввод, прежде чем он сможет продолжить. Для простоты, скажем, пользователь просто должен нажать кнопку «продолжить».
Мой вопрос: как я могу приостановить выполнение GetResults таким образом, чтобы он ожидал события, такого как нажатие другой кнопки?
Вот ужасный способ добиться того, что я ищу: обработчик события для кнопки "продолжить" устанавливает флаг ...
private bool _continue = false;
private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
_continue = true;
}
... и GetResults периодически опрашивает его:
buttonContinue.Visibility = Visibility.Visible;
while (!_continue) await Task.Delay(100); // poll _continue every 100ms
buttonContinue.Visibility = Visibility.Collapsed;
Опрос явно ужасен (занят ожиданием / тратой циклов), и я ищу что-то основанное на событиях.
Любые идеи?
Кстати, в этом упрощенном примере одним из решений, конечно, было бы разделение GetResults () на две части, вызов первой части с помощью кнопки «Пуск» и второй части с помощью кнопки «Продолжить». В действительности, вещи, происходящие в GetResults, являются более сложными, и в разных точках выполнения могут потребоваться разные типы пользовательского ввода. Так что разбить логику на несколько методов было бы нетривиально.
ManualResetEvent(Slim)
, похоже, не поддерживаетWaitAsync()
.async
не означает «работает в другом потоке» или что-то в этом роде. Это просто означает «вы можете использоватьawait
в этом методе». И в этом случае блокировка внутриGetResults()
фактически заблокирует поток пользовательского интерфейса.await
сам по себе не гарантирует создания другого потока, но заставляет все остальное после оператора выполняться как продолжениеTask
или ожидание, которое вы вызываетеawait
. Чаще всего, это какой - то вид асинхронной операции, которые могут быть IO завершение, или что - то , что находится в другом потоке.SemaphoreSlim.WaitAsync
не просто вставитьWait
поток пула потоков.SemaphoreSlim
имеет правильную очередьTask
s, которые используются для реализацииWaitAsync
.Если у вас есть необычная вещь, на которую вам нужно
await
включить, часто самый простой ответTaskCompletionSource
(или какой-нибудьasync
включенный примитив на основеTaskCompletionSource
).В этом случае ваша потребность довольно проста, поэтому вы можете просто использовать
TaskCompletionSource
напрямую:Логически
TaskCompletionSource
это похоже на anasync
ManualResetEvent
, за исключением того, что вы можете «установить» событие только один раз, и событие может иметь «результат» (в этом случае мы его не используем, поэтому мы просто устанавливаем результатnull
).источник
Вот полезный класс, который я использую:
И вот как я это использую:
источник
new Task(() => { });
будет мгновенно завершено?Простой хелпер класс:
Использование:
источник
example.YourEvent
?В идеале, вы не . Хотя вы, конечно, можете заблокировать асинхронный поток, это пустая трата ресурсов, а не идеальная.
Рассмотрим канонический пример, когда пользователь идет на обед, пока кнопка ожидает нажатия.
Если вы остановили свой асинхронный код во время ожидания ввода от пользователя, то это просто напрасная трата ресурсов, пока этот поток приостановлен.
Тем не менее, лучше, если в вашей асинхронной операции вы устанавливаете состояние, которое вам нужно поддерживать, до точки, когда кнопка включена, и вы «ждете» щелчка. В этот момент ваш
GetResults
метод останавливается .Затем, когда кнопка будет нажата, на основе состояния , которые вы сохранили, вы начинаете другую асинхронную задачу продолжить работу.
Поскольку это
SynchronizationContext
будет записано в вызывающем обработчике событийGetResults
(компилятор сделает это в результате использования используемогоawait
ключевого слова и того факта, что SynchronizationContext.Current должен быть ненулевым, если вы находитесь в приложении пользовательского интерфейса), вы можно использоватьasync
/await
нравится так:ContinueToGetResultsAsync
это метод, который продолжает получать результаты в случае нажатия вашей кнопки. Если ваша кнопка не нажата, то ваш обработчик событий ничего не делает.источник
GetResults
возвращаетTask
.await
просто говорит «запустите задачу, и когда задача будет выполнена, продолжайте код после этого». Учитывая наличие контекста синхронизации, вызов перенаправляется обратно в поток пользовательского интерфейса, поскольку он фиксируется вawait
.await
это не то же самоеTask.Wait()
, что ни в коем случае.Wait()
. Но здесьGetResults()
будет выполняться код в потоке пользовательского интерфейса, другого потока нет. Другими словами, да, вawait
принципе, задача выполняется, как вы говорите, но здесь эта задача также выполняется в потоке пользовательского интерфейса.await
а затем код послеawait
, блокировки нет. Остальная часть кода отправляется обратно в продолжение и планируется черезSynchronizationContext
.Стивен Туб опубликовал этот
AsyncManualResetEvent
класс в своем блоге .источник
С реактивными расширениями (Rx.Net)
Вы можете добавить Rx с помощью Nuget Package System.Reactive
Протестированный образец:
источник
Я использую свой собственный класс AsyncEvent для ожидаемых событий.
Чтобы объявить событие в классе, который вызывает события:
Чтобы поднять события:
Чтобы подписаться на события:
источник