UWP: как убедиться, что асинхронные события обрабатываются последовательно - PullRequest
0 голосов
/ 24 января 2019

Мне нужен обработчик событий для перетаскивания элемента в моем проекте UWP, который выполняет ожидаемые операции.

Поэтому мне нужно, чтобы мой обработчик событий был помечен как асинхронный:

myElement.PointerMoved += OnPointerMoved;

public async void OnPointerMoved(object sender, PointerRoutedEventArgs e)
{
 await MyOperationAsync(); 
}

В результате я обнаружил, что OnPointerMoved вызывается платформой UWP, даже если предыдущее выполнение не завершено (что можно предвидеть, учитывая, что вы не можете ожидать асинхронный метод void ...).

Я ищу решение, чтобы убедиться, что код в моем обработчике событий вызывается последовательно (то есть следующее выполнение OnPointerMoved должно произойти после того, как предыдущий фактически завершился).

Есть ли у кого-нибудь элегантное решение для этого?

1 Ответ

0 голосов
/ 25 января 2019

На самом деле это типичная проблема производителя / потребителя , которая имеет множество решений, распространяющихся по сети.

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

private bool _isProcessing = false;
private readonly Queue<PointerPoint> _operationQueue = new Queue<PointerPoint>();

private async Task EnqueueOperationAsync(PointerPoint point)
{
    //using the pointer point as argument of my operation in this example
    _operationQueue.Enqueue(point); 
    if (!_isProcessing)
    {
        _isProcessing = true;
        while (_operationQueue.Count != 0)
        {
            var argument = _operationQueue.Dequeue();
            await MyOperationAsync(argument);
        }
        _isProcessing = false;
    }
}

private async void UIElement_OnPointerMoved(object sender, PointerRoutedEventArgs e)
{
    await EnqueueOperationAsync(e.GetCurrentPoint(this));
}

Если вы убедитесь, что EnqueueOperationAsync вызывается только из потока пользовательского интерфейса (что имеет место, еслизапускается OnPointerMoved), это должно работать именно так, как вам нужно, благодаря тому факту, что существует только один поток пользовательского интерфейса и из-за автоматического возврата в поток пользовательского интерфейса await, единственное место, где метод EnqueueOperationAsync может оставитьПоток пользовательского интерфейса находится во время выполнения MyOperationAsync, в этом случае _isProcessing должен быть true, поэтому вновь поступающая операция будет поставлена ​​в очередь только и будет обработана после завершения MyOperationAsync и возврата к выполнению в потоке пользовательского интерфейса.Если обработать больше нечего, while завершается, когда _operationQueue становится пустым, а _isProcessing устанавливается на false - готовность к следующему событию.

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

Вы можете даже проверить это в начале метода:

if (CoreWindow.GetForCurrentThread().Dispatcher.HasThreadAccess)
   throw new InvalidOperationException(
         "This method must be called from the UI thread");

Примечание: Несмотря на то, что логика в моих тестах выглядит убедительной, я лучше тоже проверю это с кем-то еще: -)

...