简体   繁体   中英

Using cancellation token properly in c#

I was recently exposed to C# language and was working on getting data out of cassandra so I was working with below code which gets data from Cassandra and it works fine.

Only problem I have is in my ProcessCassQuery method - I am passing CancellationToken.None to my requestExecuter Function which might not be the right thing to do. What should be the right way to handle that case and what should I do to handle it correctly?

/**
 *
 * Below method does multiple async calls on each table for their corresponding id's by limiting it down using Semaphore.
 *
 */
private async Task<List<T>> ProcessCassQueries<T>(IList<int> ids, Func<CancellationToken, int, Task<T>> mapperFunc, string msg) where T : class
{
    var tasks = ids.Select(async id => 
    {
        await semaphore.WaitAsync();
        try
        {
            ProcessCassQuery(ct => mapperFunc(ct, id), msg);
        }
        finally
        {
            semaphore.Release();
        }
    });

  return (await Task.WhenAll(tasks)).Where(e => e != null).ToList();
}

// this might not be good idea to do it. how can I improve below method?
private Task<T> ProcessCassQuery<T>(Func<CancellationToken, Task<T>> requestExecuter, string msg) where T : class
{
    return requestExecuter(CancellationToken.None);
}

As said in the official documentation , the cancellation token allows propagating a cancellation signal. This can be useful for example, to cancel long-running operations that for some reason do not make sense anymore or that are simply taking too long.

The CancelationTokenSource will allow you to get a custom token that you can pass to the requestExecutor . It will also provide the means for cancelling a running Task .

private CancellationTokenSource cts = new CancellationTokenSource();

// ...

private Task<T> ProcessCassQuery<T>(Func<CancellationToken, Task<T>> requestExecuter, string msg) where T : class
{
    return requestExecuter(cts.Token);
}

Example

Let's take a look at a different minimal/dummy example so we can look at the inside of it.

Consider the following method, GetSomethingAsync that will yield return an incrementing integer every second.

The call to token.ThrowIfCancellationRequested will make sure a TaskCanceledException is thrown if this process is cancelled by an outside action. Other approaches can be taken, for example, check if token.IsCancellationRequested is true and do something about it.

private static async IAsyncEnumerable<int> GetSomethingAsync(CancellationToken token)
{
    Console.WriteLine("starting to get something");

    token.ThrowIfCancellationRequested();

    for (var i = 0; i < 100; i++)
    {
        await Task.Delay(1000, token);
        yield return i;
    }

    Console.WriteLine("finished getting something");
}

Now let's build the main method to call the above method.

public static async Task Main()
{    
    var cts = new CancellationTokenSource();

    // cancel it after 3 seconds, just for demo purposes
    cts.CancelAfter(3000);
    // or: Task.Delay(3000).ContinueWith(_ => { cts.Cancel(); });

    await foreach (var i in GetSomethingAsync(cts.Token))
    {
        Console.WriteLine(i);
    }
}

If we run this, we will get an output that should look like:

starting to get something
0
1
Unhandled exception. System.Threading.Tasks.TaskCanceledException: A task was canceled.

Of course, this is just a dummy example, the cancellation could be triggered by a user action, or some event that happens, it does not have to be a timer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM