Лучшая практика для последовательной отмены Async CancellationTokenSource

Итак, у меня есть поле со списком в моем пользовательском интерфейсе, которое в SelectionChanged асинхронно переходит в веб-службу, чтобы получить некоторую информацию, которая будет отображаться в пользовательском интерфейсе (с использованием новых ключевых слов C # 5 async / await). Я хочу отменить текущий асинхронный запрос перед отправкой нового; например, если пользователь использует клавиатуру для быстрого просмотра всех элементов поля со списком, событие SelectionChanged может запускаться несколько раз (генерируя несколько асинхронных запросов) до того, как вернется даже первый асинхронный запрос.

Итак, моя асинхронная функция, которая вызывается из события SelectionChanged поля со списком, выглядит так:

public async Task<Connections> ConnectionsAsync()
{
    return await Task.Factory.StartNew(() => Connections, _cancellationTokenSource.Token);
}

Где Connections - это свойство, которое отключается и попадает в веб-службу. Итак, поскольку CancellationTokenSource не может быть повторно использован после отмены, я думаю сделать это:

public async Task<Connections> ConnectionsAsync()
{
    _cancellationTokenSource.Cancel();
    _cancellationTokenSource = new CancellationTokenSource();
    return await Task.Factory.StartNew(() => Connections, _cancellationTokenSource.Token);
}

Проблема, однако, в том, что иногда я вызываю Cancel (), когда не запущена асинхронная команда (например, при первом вызове этой функции); поэтому, если я подключу какие-либо обработчики событий отмены, они будут вызваны, даже до того, как я сделаю асинхронный запрос.

Есть ли способ проверить, запущен ли уже асинхронный запрос? Кроме меня, делаю что-то вроде:

public async Task<Connections> ConnectionsAsync()
{
    if (_runningAsyncCommand)
        _cancellationTokenSource.Cancel();
    _cancellationTokenSource = new CancellationTokenSource();
    _runningAsyncCommand = true;
    return await Task.Factory.StartNew(() => Connections, _cancellationTokenSource.Token);
    _runningAsyncCommand = false;
}

У меня есть несколько асинхронных функций, которые все используют один и тот же CancellationTokenSource, поэтому мне пришлось бы реализовать эту «сантехнику» во всех этих функциях. Это лучший подход? Или есть способ лучше?

Кроме того, если я открываю _cancellationTokenSource публично, чтобы другие классы могли регистрировать с ним делегаты отмены, как лучше всего «передать» эти делегаты в новый CancellationTokenSource, поскольку я каждый раз создаю новый?

Заранее спасибо!


person deadlydog    schedule 07.07.2012    source источник
comment
Было ли решено? У меня сейчас точно такой же вопрос.   -  person NS.X.    schedule 14.08.2012
comment
Нет, это не решено, но, оглядываясь на свой вопрос, я думаю, что правильнее всего сделать токен CancellationTokenSource в качестве необязательного параметра (например, общедоступную асинхронную задачу ‹Connections› ConnectionsAsync (токен CancellationToken = null)). Таким образом, пользователь, вызывающий функцию ConnectionsAsync (), отвечает за настройку нового CancellationTokenSource и передачу его Token.   -  person deadlydog    schedule 14.08.2012
comment
Итак, в моем примере это произошло бы в событии SelectionChanged моего поля со списком; Я бы вызвал Cancel () для существующего CancellationTokenSource, создал новый и передал его Token в функцию ConnectionsAsync (). В конце концов, для моего конкретного приложения я не использовал отмену.   -  person deadlydog    schedule 14.08.2012
comment
Спасибо за обновления. Я буду продолжать поиски, так как это распространенный шаблон в моем приложении пользовательского интерфейса.   -  person NS.X.    schedule 15.08.2012


Ответы (1)


Похоже на то, что Reactive Extensions заключили брак на небесах. Определите время дросселирования, которое должно истечь (скажем, 300 мс) при наблюдении за событиями из поля со списком перед созданием задачи.

Фрагмент кода, подписывающийся на события изменения TextBox, но вы поймете идею:

var input (from evt in Observable.FromEvent<EventArgs>(txt, "TextChanged")
select ((TextBox)evt.Sender).Text)
.Throttle(TimeSpan.FromSeconds(1))
.DistinctUntilChanged();

using (input.Subscribe(inp => Console.WriteLine("You wrote: " + inp)))
{
  // Do stuff
}
person Magnus Johansson    schedule 07.07.2012
comment
Хорошее решение, но все же не идеальное. Скажем, мой запрос к веб-службе возвращается через 3 секунды. Пользователь может по-прежнему изменять выбор поля со списком каждые 2 секунды, и в этом случае у меня все еще есть та же проблема. Я не хочу устанавливать дроссельную заслонку слишком высоко, например, на 2 секунды, иначе каждый раз, когда они меняют выбор, будет задержка в 2 секунды, прежде чем он даже отправит запрос; итого 5 секунд с момента изменения выбора до момента обновления пользовательского интерфейса. - person deadlydog; 07.07.2012
comment
Рассматривали ли вы реализацию механизма (предварительно загруженного) локального кеша? Вам действительно нужно получать данные в реальном времени из выбора изменения поля со списком? - person Magnus Johansson; 08.07.2012
comment
Я мог бы обойтись без кеширования данных для моего конкретного случая, поскольку есть только около 75 человек, которые могут изменять данные, которые я получаю. Однако исходный вопрос все еще актуален; Предположим, в моем поле со списком перечислены мои учетные записи Twitter, и когда я выбрал одну из них, я хотел увидеть ее прямую ленту. Это прекрасный пример, когда вам всегда нужны данные в реальном времени. - person deadlydog; 08.07.2012
comment
Исходный вопрос может быть правильным, но конкретная реализация, которую вы упомянули, - плохой дизайн. С какой стати вы хотите принудительно обновлять обновления в реальном времени по сети, когда пользователь просто переключал просмотры учетной записи через поле со списком? Внедрите локальный кеш для каждого канала учетной записи Twitter с последним обновленным полем даты и времени и выполняйте сервисные вызовы только тогда, когда срок действия этого кеша истек для каждой учетной записи. В качестве альтернативы (даже лучше) периодически делайте фоновые обновления каждого потока. Постарайтесь не усложнять, ваша исходная реализация Задачи кажется слишком сложной для конкретного сценария. - person Magnus Johansson; 08.07.2012