Каков правильный способ отмены асинхронной операции, которая не принимает CancellationToken?

Каков правильный способ отменить следующее?

var tcpListener = new TcpListener(connection); tcpListener.Start(); var client = await tcpListener.AcceptTcpClientAsync(); 

Просто вызов tcpListener.Stop() похоже, приводит ObjectDisposedException AcceptTcpClientAsync метод AcceptTcpClientAsync не принимает структуру CancellationToken .

Неужели я совершенно ничего не вижу?

Предполагая, что вы не хотите вызывать метод Stop в classе TcpListener , здесь нет идеального решения.

Если вы в порядке с уведомлением о том, что операция не завершена в течение определенного периода времени, но позволяет выполнить первоначальную операцию, вы можете создать метод расширения, например:

 public static async Task WithWaitCancellation( this Task task, CancellationToken cancellationToken) { // The tasck completion source. var tcs = new TaskCompletionSource(); // Register with the cancellation token. using(cancellationToken.Register( s => ((TaskCompletionSource)s).TrySetResult(true), tcs) ) { // If the task waited on is the cancellation token... if (task != await Task.WhenAny(task, tcs.Task)) throw new OperationCanceledException(cancellationToken); } // Wait for one or the other to complete. return await task; } 

Вышеупомянутое сообщение блога Стивена Тоуба «Как отменить отмененные асинхронные операции?» ,

Оговорка здесь повторяется, это фактически не отменяет операцию, потому что не существует перегрузки метода AcceptTcpClientAsync который принимает CancellationToken , его нельзя отменить.

Это означает, что если метод расширения указывает на то, что отмена произошла, вы отменяете ожидание обратного вызова исходной Task , не отменяя сама операция.

С этой целью я переименовал метод из WithCancellation в WithWaitCancellation чтобы указать, что вы отменяете ожидание , а не фактическое действие.

Оттуда это легко использовать в вашем коде:

 // Create the listener. var tcpListener = new TcpListener(connection); // Start. tcpListener.Start(); // The CancellationToken. var cancellationToken = ...; // Have to wait on an OperationCanceledException // to see if it was cancelled. try { // Wait for the client, with the ability to cancel // the *wait*. var client = await tcpListener.AcceptTcpClientAsync(). WithWaitCancellation(cancellationToken); } catch (AggregateException ae) { // Async exceptions are wrapped in // an AggregateException, so you have to // look here as well. } catch (OperationCancelledException oce) { // The operation was cancelled, branch // code here. } 

Обратите внимание, что вам придется обернуть вызов для вашего клиента, чтобы захватить экземпляр OperationCanceledException если ожидание будет отменено.

Я также AggregateException catch AggregateException поскольку исключения были завернуты при асинхронных операциях (в этом случае вы должны проверить себя).

Это оставляет вопрос о том, какой подход является лучшим подходом перед лицом метода, подобного методу « Stop (в основном, все, что сильно срывает все, независимо от того, что происходит), что, конечно, зависит от ваших обстоятельств.

Если вы не используете ресурс, который вы ожидаете (в данном случае, TcpListener ), то, вероятно, было бы лучше использовать ресурсы для вызова метода прерывания и проглатывания любых исключений, возникающих в результате операций, которые вы ожидаете (вам придется перевернуть бит, когда вы вызываете стоп и контролируете этот бит в других областях, которые вы ожидаете при выполнении операции). Это добавляет некоторую сложность коду, но если вы беспокоитесь об использовании ресурсов и их очистке как можно скорее, и этот выбор доступен вам, то это путь.

Если использование ресурсов не является проблемой, и вам удобнее использовать более совместный механизм, и вы не используете ресурс, то использование метода WithWaitCancellation прекрасное. Плюсы здесь состоят в том, что это более чистый код и проще в обслуживании.

В то время как ответ casperOne верен, существует более чистая потенциальная реализация метода расширения WithCancellation (или WithWaitCancellation ), который достигает тех же целей:

 static Task WithCancellation(this Task task, CancellationToken cancellationToken) { return task.IsCompleted ? task : task.ContinueWith( completedTask => completedTask.GetAwaiter().GetResult(), cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } 
  • Сначала у нас есть оптимизация быстрого пути, проверяя, завершена ли задача.
  • Затем мы просто регистрируем продолжение первоначальной задачи и передаем параметр CancellationToken .
  • Продолжение извлекает исходную задачу (или исключение, если она есть) синхронно, если это возможно ( TaskContinuationOptions.ExecuteSynchronously ) и использует ThreadPool stream, если нет ( TaskScheduler.Default ) при наблюдении CancellationToken для отмены.

Если исходная задача завершается до CancellationToken тогда возвращаемая задача сохраняет результат, иначе задача отменяется и при ожидании будет TaskCancelledException .

  • Запуск задач в foreach Loop использует значение последнего элемента
  • Лучший способ конвертировать метод async на основе обратного вызова в ожидаемую задачу
  • Запуск двух асинхронных задач параллельно и сбор результатов в .NET 4.5
  • Шаблон для самоотдачи и перезапуска задачи
  • Выполнение задач параллельно
  • Почему это асинхронное действие зависает?
  • WaitAll vs WhenAll
  • Interesting Posts

    Как вызвать хранимую процедуру oracle, которая включает пользовательский тип в java?

    Репрезентативная передача состояния (REST) ​​и протокол простого доступа к объектам (SOAP)

    Используйте CSS для автоматического добавления звездочки «обязательное поле» для формирования входных данных

    Как работает кодировка с переменной шириной UTF-8?

    Размеры экрана Android в пикселях для ldpi, mdpi, hpdi?

    Сенсорная панель не реагирует, когда я держу клавишу на клавиатуре

    Материнская плата Gigabyte (890gpa-ud3h) не отключит питание USB

    Изменение SSID при подключении клиентов

    Пользовательский отчет JUnit?

    Как сообщить Spring Boot, какой основной class использовать для исполняемого банку?

    Как создать матрицу корреляции в R?

    Код ошибки: 1215. Невозможно добавить ограничение внешнего ключа (foreign keys)

    Как запустить файл bat в фоновом режиме из другого файла bat?

    Бесплатный файл заблокирован новым Bitmap (filePath)

    Каков наилучший способ запуска события onchange в реакции js

    Давайте будем гением компьютера.