Task.Factory.FromAsync с CancellationTokenSource

У меня есть следующая строка кода, используемая для асинхронного чтения из NetworkStream:

int bytesRead = await Task<int>.Factory.FromAsync(this.stream.BeginRead, this.stream.EndRead, buffer, 0, buffer.Length, null);

Я хотел бы, чтобы это поддерживало отмену. Я вижу, что могу отменить задачи, используя CancellationTokenSource, однако я не вижу способа передать его в TaskFactory.FromAsync().

Можно ли отменить поддержку поддержки построенной задачи FromAsync()?

Изменить: я хочу отменить задачу, которая уже выполняется.

3 ответа

Решение

Gigi, к сожалению, семантическая природа FromAsync указывает на то, что вы только адаптируете асинхронный процесс к API TPL (TPL = параллельная библиотека задач Microsoft)

По сути, TPL ReadAsync управляет самим поведением асинхронности, тогда как FromAsync только оборачивает поведение (но не управляет им).

Теперь, поскольку Cancellation является специфичной для TPL конструкцией, а поскольку FromAsync не контролирует внутреннюю работу вызываемого асинхронного метода, не существует гарантированного способа чистой отмены задачи и обеспечения правильного закрытия всех ресурсов (поэтому был опущен. Если вам интересно, просто декомпилируйте метод;))

В этих ситуациях имеет смысл обернуть фактический асинхронный вызов самостоятельно в обычную задачу и обнаружить исключение OperationCancelled, которое даст вам возможность закрыть ваш поток, выполнив соответствующие вызовы.

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

Как уже упоминали другие, нет чистого способа достичь того, что вы просите. Понятие отмены отсутствовало в модели асинхронного программирования; таким образом, он не мог быть модернизирован через FromAsync преобразователи.

Тем не менее, вы можете ввести отмену для Task это оборачивает асинхронную операцию. Это не отменяет саму базовую операцию - ваш NetworkStream все равно продолжит чтение всех запрошенных байтов из сокета - но это позволит вашему приложению реагировать так, как если бы операция была отменена, немедленно выбрасывая OperationCanceledException от твоего await (и выполнение любых зарегистрированных продолжений задачи). Результат основной операции после ее завершения будет проигнорирован.

Это вспомогательный метод расширения:

public static class TaskExtensions
{
    public async static Task<TResult> HandleCancellation<TResult>(
        this Task<TResult> asyncTask,
        CancellationToken cancellationToken)
    {     
        // Create another task that completes as soon as cancellation is requested.
        // http://stackru.com/a/18672893/1149773
        var tcs = new TaskCompletionSource<TResult>();
        cancellationToken.Register(() =>
            tcs.TrySetCanceled(), useSynchronizationContext: false);
        var cancellationTask = tcs.Task;

        // Create a task that completes when either the async operation completes,
        // or cancellation is requested.
        var readyTask = await Task.WhenAny(asyncTask, cancellationTask);

        // In case of cancellation, register a continuation to observe any unhandled 
        // exceptions from the asynchronous operation (once it completes).
        // In .NET 4.0, unobserved task exceptions would terminate the process.
        if (readyTask == cancellationTask)
            asyncTask.ContinueWith(_ => asyncTask.Exception, 
                TaskContinuationOptions.OnlyOnFaulted | 
                TaskContinuationOptions.ExecuteSynchronously);

        return await readyTask;
    }
}

И это пример, который использует метод расширения для обработки операции как отмененной после 300 мс:

CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromMilliseconds(300));

try
{
    int bytesRead = 
        await Task<int>.Factory.FromAsync(this.stream.BeginRead, this.stream.EndRead, buffer, 0, buffer.Length, null)
                               .HandleCancellation(cts.Token);
}
catch (OperationCanceledException)
{
    // Operation took longer than 300ms, and was treated as cancelled.
}

Нет, нет общего способа отменить такую ​​задачу. Отмена зависит от API.

  • Например, WebClient имеет Cancel метод.
  • Socket или FileStream должно быть Closeхотел бы отменить невыполненный звонок.
  • У клиентов веб-сервисов есть даже разные способы прерывания вызовов.
  • ...

Это потому, что разработчик операции ввода-вывода должен поддерживать отмену.

Может показаться заманчивым использовать NetworkStream.ReadAsync и передать токен отмены, но Stream.ReadAsync, Последний просто выбрасывает токен. В основном не поддерживается.

Stream.ReadAsync это просто метод базового класса. Он ничего не делает сам по себе. Конкретные операции ввода-вывода выдаются только по производным классам. Те должны поддержать отмену изначально. Stream не может ничего сделать, чтобы заставить их. Бывает что NetworkStream не поддерживает отмену.

Я понимаю, что вы хотите отменить операцию и оставить сокет открытым. Но это невозможно. (Субъективное примечание: это действительно печальное положение дел. Особенно с учетом того, что Windows поддерживает отменяемый ввод-вывод на уровне Win32.)

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

"Отмена игнорированием" фактически делает позицию потока неопределенной. Поток становится непригодным для использования. Это на самом деле не избавляет от необходимости открывать новый поток. Вам все равно придется избавиться от старого потока (в большинстве случаев) и снова открыть. Кроме того, вы вводите параллелизм.

Другие вопросы по тегам