Подтвердить что ты не робот

Можно ли отменить 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 символов в одну строку (например, при сбрасывании всей строки в файл).

4b9b3361

Ответ 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);
}