Повторяемые запросы и CancellationToken/CancellationTokenSource

Я добавляю новую функциональность в приложение Windows Forms, где после того, как пользователь заполнит набор адресных полей, я жду несколько секунд, прежде чем запросить геокод этого адреса через API геокодирования переписи населения США. Возможно, что пользователь не введет правильную информацию с первого раза и ему потребуется исправить данные и повторно запросить. Когда это произойдет, я хочу отменить запрос и отправить новый. Запрос запускается из обратного вызова события System.Timers.Timer, который в настоящее время использует существующий CancellationTokenSource, хранящийся в поле в модели представления формы, и передает токен CancellationTokenSource в Task.Run и в асинхронную лямбда-функцию, которая работает следующим образом:

private void OnServiceAddressChangedTimerElapsed(object sender, System.Timers.ElapsedEventArgs args)
{
    Task.Run(async () => {
        var geocode = await Geocoder.GetGeocodeAsync(
            ServiceAddress.Line1,
            ServiceAddress.Line2,
            ServiceAddress.City,
            ServiceAddress.State,
            ServiceAddress.Zip,
            _gpsLookupCancellationSource.Token
        );

        GPSCoordinates.Latitude = geocode?.Latitude;
        GPSCoordinates.Longitude = geocode?.Longitude;
    }, _gpsLookupCancellationSource.Token);
}

Проблема, с которой я сталкиваюсь, заключается в функции OnServiceAddressChanged (в настоящее время вызывается каждый раз, когда изменяется текст в полях адреса, который в конечном итоге будет изменен на либо когда пользователь покидает поле, либо срабатывает с помощью кнопки, но в любом случае), когда изменения данных, я отменяю _gpsLookupCancellationSource, поэтому, когда я повторно использую его позже в функции OnServiceAddressChangedTimerElapsed, он уже отменен:

private void OnServiceAddressChanged(object sender, EventArgs args)
{
    if (!String.IsNullOrWhiteSpace(ServiceAddress.Line1) &&
        !String.IsNullOrWhiteSpace(ServiceAddress.City) &&
        !String.IsNullOrWhiteSpace(ServiceAddress.State) &&
        !String.IsNullOrWhiteSpace(ServiceAddress.Zip)
    )
    {
        _serviceAddressChangedTimer.Stop();
        _gpsLookupCancellationSource.Cancel();
        _serviceAddressChangedTimer.Start();
    }
}

Хотя я думаю, что понимаю, почему это не работает прямо сейчас (потому что CTS уже был отменен после первого изменения поля), я не понимаю, как правильно обрабатывать _gpsLookupCancellationSource. Я думаю, что когда функция OnServiceAddressChanged запускается, она должна отменить старый источник, а затем создать новый перед перезапуском таймера, но я не могу его уничтожить, потому что, насколько я понимаю, созданные из него токены, вероятно, содержат ссылку на этот источник, чтобы знать что он был удален, и проверка его отмены, скорее всего, приведет к ObjectDisposedException. Как правильно это сделать? Можно ли избавиться от CTS, пока его токены проверяют или не проверяют свои свойства IsCancellationRequested? Нужно ли мне явно распоряжаться CTS? Есть ли гораздо более простой способ сделать все это, что я полностью упустил из виду? Спасибо всем, кто читает это за ваше время!


person Sean    schedule 04.05.2018    source источник
comment
«Выстрелил и забыл» Task уродливы. await их. Вы должны создавать новый экземпляр CancellationTokenSource каждый раз, когда запускаете Task. И не забывайте наконец Dispose его каждый раз.   -  person dymanoid    schedule 04.05.2018
comment
@dymanoid Спасибо за ваши комментарии, этот пост ответил на мой вопрос. Я знаю, что должен ждать задачи, но я не знаю, как это сделать, когда задача запускается из синхронной функции. Теперь, когда я знаю, что вы можете отменить источник, и токены, ссылающиеся на него, по-прежнему будут работать, я думаю, что знаю, что делать.   -  person Sean    schedule 04.05.2018