2015-02-20 3 views
13

Когда я отменяю свой метод асинхронизации со следующим контентом, вызывая метод Cancel() моего CancellationTokenSource, он в конечном итоге остановится. Однако, так как строка Console.WriteLine(await reader.ReadLineAsync()); занимает совсем немного времени, я попытался передать свой CancellationToken на ReadLineAsync() (ожидая, что он вернет пустую строку), чтобы сделать метод более отзывчивым к моему вызову Cancel(). Однако я не смог пройти CancellationToken до ReadLineAsync().Могу ли я отменить StreamReader.ReadLineAsync с CancellationToken?

Могу ли я отменить звонок до 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()); 
    } 
} 

Update Как указано в комментариях ниже, то Console.WriteLine() вызов в одиночку уже занимают несколько секунд из-за плохо отформатированные строки ввода 40.000 символов в строке. Нарушение этого решения решает мои проблемы с ответом, но меня все еще интересуют любые предложения или обходные пути о том, как отменить этот долговременный оператор, если по какой-то причине было записано 40 000 символов в одну строку (например, при сбрасывании всей строки в файл).

+3

Вы пытаетесь решить проблему неправильно. Вы написали чрезвычайно удобную для пользователя программу, она безумно прокручивает текст по экрану со скоростью намного выше, чем пользователь может прочитать. Теперь вы ищете решение «прекратить это глупость». Конечно, вы тоже, ваш пользователь тоже. Но это не настоящее решение, а правильное никогда не начнется в первую очередь. Напишите его в файл, выведите его в блокнот. Все лучше. –

+0

'Вы пытаетесь решить неправильную проблему.' Спасибо за то, что вы поняли - вы абсолютно правы. –

ответ

6

Вы не можете отменить операцию, если она не будет отменена. Вы можете использовать метод 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 «s 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); 
} 
+0

@HW вы не можете отменить 'Console.WriteLine', если он не поддерживает отмену, но вы можете использовать' Console.Write' понемногу. См. Мое обновление. – i3arnon

+0

Вы уверены, что 'Task.ContinueWith' также отменит все таксы? С моей точки зрения, 'Task.ContinueWith' здесь не подходит. Вам нужно что-то вроде 'Task.WhenAny (task, infinCancellableTaks)' где 'var infinCancellableTaks = Task.Delay (Timeout.Infinite, токен)', как в этом примере http://stackoverflow.com/a/23473779/2528649 – neleus

+0

@ neleus Да. Он не отменяет исходную задачу (потому что вы не можете), но она отменяет продолжение. Не стесняйтесь попробовать. – i3arnon

0

Вы не можете отменить 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); 
} 
+0

Спасибо за подсказку, что мне не нужно проверять 'IsCancellationRequested'! К сожалению, проверка снова на отмену между двумя задачами едва сократила время отклика. –

+2

Чтение одной строки может быть очень медленным, если, например, вы читаете поток stdout или сетевой поток, то есть любой поток, содержимое которого не полностью реализовано в один снимок. – dcstraw

1

Я обобщил этот answer к этому:

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; 
     } 
    } 
} 

Теперь вы можете использовать ваш отменить маркер на любой отмененный асинхронный метод. Например 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

+0

В примере есть гонка. Если вы проверите 'IsCancellationRequested' в обработчике улова, вы не знаете, произошло ли сначала аннулирование или исключение. Вы должны проверить тип исключения, чтобы убедиться, что это действительно исключение отмены. – Kenneth

Смежные вопросы