Как правильно отменить параллельную асинхронную задачу ввода-вывода с помощью клавиши Escape?

Я изучаю асинхронные операции ожидания и нашел очень полезную статью.

Я рассматриваю последний фрагмент кода из этой статьи:

public async Task ProcessWriteMult(CancellationToken cancellationToken)
{
string folder = @"tempfolder\";
List<Task> tasks = new List<Task>();
List<FileStream> sourceStreams = new List<FileStream>();

try
{
    for (int index = 1; index <= 10; index++)
    {
        string text = "In file " + index.ToString() + "\r\n";

        string fileName = "thefile" + index.ToString("00") + ".txt";
        string filePath = folder + fileName;

        byte[] encodedText = Encoding.Unicode.GetBytes(text);

        FileStream sourceStream = new FileStream(filePath,
            FileMode.Append, FileAccess.Write, FileShare.None,
            bufferSize: 4096, useAsync: true);

        Task theTask = sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
        sourceStreams.Add(sourceStream);

        tasks.Add(theTask);
    }

    await Task.WhenAll(tasks);
}

finally
{
    foreach (FileStream sourceStream in sourceStreams)
    {
        sourceStream.Close();
    }
}
}

Я пробовал какой-то код для отмены задач, однако, имхо, это очень некрасивые попытки. В этой попытке мой программный поток ожидает нажатия клавиши пользователем, но я хотел бы, чтобы этот код работал до тех пор, пока пользователь не нажмет кнопку Esc, например, событие:

if(Console.ReadKey().Key==System.ConsoleKey.Escape)
    cts.Cancel();

Как я могу правильно отменить нажатие клавиши Escape?

ОБНОВИТЬ:

Я вставил этот код:

static void Main(string[] args)
{
    var cts=new CancellationTokenSource();
    ProcessWriteMult(cts);
    if(Console.ReadKey().Key == System.ConsoleKey.Escape)
        cts.Cancel();
}

Однако этот код не отменяет метод ProcessWriteMult(cts). Что я делаю не так?


person StepUp    schedule 23.06.2015    source источник
comment
Изучили ли вы BackgroundWorker? Возможно, он лучше всего подходит для вашего сценария, поскольку поддерживает отмену по умолчанию и также выполняет работу в отдельном потоке. msdn.microsoft.com/en-us/library/ См. также: codeproject.com/Articles/20627/   -  person bit    schedule 23.06.2015


Ответы (2)


Во-первых, не используйте async void. Я не уверен, почему пример MSDN поощряет плохую практику, но все же не делайте этого. Использование async void означает, что ваш метод "запустил и забыл", его нельзя асинхронно ожидать, и любое исключение, которое происходит внутри него, будет выброшено в поток пула потоков. Вместо этого используйте async Task.

Теперь в .NET 4.0 представлена ​​концепция CancellationTokenSource, которая помогает выполнять совместную отмену. Это означает, что вы должны следить за токеном, чтобы знать, что кто-то «извне» запросил отмену. Это можно сделать следующим образом:

try
{
    for (int index = 1; index <= 10; index++)
    {
        // Monitor the token, you decide where.
        cancellationToken.ThrowIfCancellationRequested();
        string text = "In file " + index.ToString() + "\r\n";

        string fileName = "thefile" + index.ToString("00") + ".txt";
        string filePath = folder + fileName;

        byte[] encodedText = Encoding.Unicode.GetBytes(text);

        FileStream sourceStream = new FileStream(filePath,
            FileMode.Append, FileAccess.Write, FileShare.None,
            bufferSize: 4096, useAsync: true);

        Task theTask = sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
        sourceStreams.Add(sourceStream);

        tasks.Add(theTask);
    }

    await Task.WhenAll(tasks);
}

Однако этот код не отменяет метод ProcessWriteMult(cts). Что я делаю не так?

Может случиться так, что к тому времени, когда вы нажмете escape, метод уже завершит выполнение. Вы можете проверить это, ожидая Task.Delay через определенный интервал (скажем, 5 секунд) и только затем отслеживая токен отмены.

person Yuval Itzchakov    schedule 23.06.2015
comment
Событие Console.CancelKeyPress может быть лучшей альтернативой опросу, если OP готов обменять Esc на Ctrl-C. В противном случае +1 :) - person noseratio; 23.06.2015
comment
@Noseratio ОП сегодня испорчены. Все дело в Esc ;p - person Yuval Itzchakov; 23.06.2015

Для отмены требуется два типа: один — источник, другой — слушатель. т.е. вам нужен экземпляр CancellationTokenSource, который вызовет отмену, и ваш код должен прослушивать отмену через токен:

Task ProcessWriteMultAsync(CancellationTokenSource source)
{
  // ...
}

var cts = new CancellationTokenSource();
await ProcessWriteMultAsync(cts.Token);
if(Console.ReadKey().Key==System.ConsoleKey.Escape)
           cts.Cancell();

И пожалуйста, не используйте async void! Их следует использовать только для обработчиков событий.

person Sergey Teplyakov    schedule 23.06.2015