How to properly cancel parallel asynchronous IO Task by Escape key?

1.2k views Asked by At

I am learning async await operations and have found a very useful article.

I consider the the last code snippet from this article:

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

I have tried some code to cancel tasks, however, imho, these are very ugly attempts. In this attempt my program flow waits for pressing key by user, but I would like this code to work till user press Esc button, like event:

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

How can I properly cancel by Escape key?

UPDATE:

I've inserted this code:

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

However, this code does not cancel method ProcessWriteMult(cts). What am I doing wrong?

2

There are 2 answers

2
Yuval Itzchakov On BEST ANSWER

First off, don't use async void. I'm not sure why the MSDN example encourages bad practice, but still, don't do that. Using async void means you're method is "fire and forget", it can't be asynchronously waited, and any exception that happens inside it will be thrown on a thread-pool thread. Instead, use async Task.

Now, .NET 4.0 introduced the concept of CancellationTokenSource, which helps you do cooperative cancellation. This means that you have to monitor on the token in order to know someone from "the outside" requested cancellation. This can be done as follows:

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

However, this code does not cancel method ProcessWriteMult(cts). What am I doing wrong?

It could be that by the time you press escape, the method already finished executing. You can test this by awaiting Task.Delay on a certain interval (lets say, 5 seconds), and only then monitoring the cancellation token.

0
Sergey Teplyakov On

There is two types required for cancellation: one is the source another is a listener. I.e. you need an instance of CancellationTokenSource that would trigger the cancellation and your code should listen for the cancellation via the token:

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

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

And please, do not use async void! They should only be used for event handlers.