Можно ли отменить StreamReader.ReadLineAsync с CancellationToken?
Когда я отменяю свой асинхронный метод со следующим контентом, вызывая метод Cancel()
моего CancellationTokenSource
, он в конечном итоге остановится. Однако, так как строка Console.WriteLine(await reader.ReadLineAsync());
занимает совсем немного времени, я попытался передать свой CancellationToken на ReadLineAsync()
(ожидая, что он вернет пустую строку), чтобы сделать метод более восприимчивым к моему вызову Cancel()
. Однако я не мог передать CancellationToken
в ReadLineAsync()
.
Можно ли отменить вызов Console.WriteLine()
или Streamreader.ReadLineAsync()
, и если да, как это сделать?
Почему ReadLineAsync()
не принимает CancellationToken
? Я подумал, что хорошей практикой является предоставление Async-методам необязательного параметра CancellationToken
, даже если метод по-прежнему завершается после отмены.
StreamReader reader = new StreamReader(dataStream);
while (!reader.EndOfStream)
{
if (ct.IsCancellationRequested){
ct.ThrowIfCancellationRequested();
break;
}
else
{
Console.WriteLine(await reader.ReadLineAsync());
}
}
Обновление
Как указано в комментариях ниже, только вызов Console.WriteLine()
уже занимал несколько секунд из-за плохо сформированной входной строки из 40 000 символов в строке. Нарушение этого решения решает мои проблемы с ответом, но меня все еще интересуют любые предложения или обходные пути о том, как отменить этот долговременный оператор, если по какой-то причине было записано 40 000 символов в одну строку (например, при сбрасывании всей строки в файл).
Ответы
Ответ 1
Вы не можете отменить операцию, если она не будет отменена. Вы можете использовать метод расширения WithCancellation
, чтобы вести поток вашего кода, как если бы он был отменен, но базовое значение все равно будет выполняться:
public static Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
return task.IsCompleted // fast-path optimization
? task
: task.ContinueWith(
completedTask => completedTask.GetAwaiter().GetResult(),
cancellationToken,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}
Использование:
await task.WithCancellation(cancellationToken);
Вы не можете отменить Console.WriteLine
, и вам не нужно. Это мгновенно, если у вас есть разумный размер string
.
О руководстве: Если ваша реализация фактически не поддерживает отмену, вы не должны принимать токен, поскольку он отправляет смешанное сообщение.
Если у вас есть огромная строка для записи на консоль, вы не должны использовать Console.WriteLine
. Вы можете написать строку в символе за раз и отказаться от этого метода:
public void DumpHugeString(string line, CancellationToken token)
{
foreach (var character in line)
{
token.ThrowIfCancellationRequested();
Console.Write(character);
}
Console.WriteLine();
}
Еще лучшим решением было бы писать партиями вместо одиночных символов. Здесь реализация с использованием MoreLinq
Batch
:
public void DumpHugeString(string line, CancellationToken token)
{
foreach (var characterBatch in line.Batch(100))
{
token.ThrowIfCancellationRequested();
Console.Write(characterBatch.ToArray());
}
Console.WriteLine();
}
Итак, в заключение:
var reader = new StreamReader(dataStream);
while (!reader.EndOfStream)
{
DumpHugeString(await reader.ReadLineAsync().WithCancellation(token), token);
}
Ответ 2
Я обобщил этот ответ на это:
public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken, Action action, bool useSynchronizationContext = true)
{
using (cancellationToken.Register(action, useSynchronizationContext))
{
try
{
return await task;
}
catch (Exception ex)
{
if (cancellationToken.IsCancellationRequested)
{
// the Exception will be available as Exception.InnerException
throw new OperationCanceledException(ex.Message, ex, cancellationToken);
}
// cancellation hasn't been requested, rethrow the original Exception
throw;
}
}
}
Теперь вы можете использовать маркер отмены для любого метода отмены async. Например, WebRequest.GetResponseAsync:
var request = (HttpWebRequest)WebRequest.Create(url);
using (var response = await request.GetResponseAsync())
{
. . .
}
станет:
var request = (HttpWebRequest)WebRequest.Create(url);
using (WebResponse response = await request.GetResponseAsync().WithCancellation(CancellationToken.None, request.Abort, true))
{
. . .
}
См. пример http://pastebin.com/KauKE0rW
Ответ 3
Вы не можете отменить Streamreader.ReadLineAsync()
. ИМХО это потому, что чтение одной строки должно быть очень быстрым. Но вы можете легко предотвратить событие Console.WriteLine()
, используя отдельную переменную задачи.
Проверка на ct.IsCancellationRequested
также дублируется и как ct.ThrowIfCancellationRequested()
будет только бросаться, если требуется аннулирование.
StreamReader reader = new StreamReader(dataStream);
while (!reader.EndOfStream)
{
ct.ThrowIfCancellationRequested();
string line = await reader.ReadLineAsync());
ct.ThrowIfCancellationRequested();
Console.WriteLine(line);
}