Search code examples
c#azure-cloud-servicescancellationtokensource

Why is this CancellationToken getting cancelled?


It seems like right after I call my first async method (GetBar() in this example), the CancellationToken's IsCancellationRequested is set to true, but I don't want that and don't understand why it's happening.

This is in an Azure Cloud Service worker role, if that matters.

public class WorkerRole : RoleEntryPoint
{
    private CancellationTokenSource cancellationTokenSource;
    private Task runTask;

    public override void Run()
    {
        this.cancellationTokenSource = new CancellationTokenSource();
        this.runTask = Task.Run(() => Foo.Bar(this.cancellationTokenSource.Token), this.cancellationTokenSource.Token);
    }

    public override void OnStop()
    {
        this.cancellationTokenSource.Cancel();

        try
        {
            this.runTask.Wait();
        }
        catch (Exception e)
        {
            Logger.Error(e, e.Message);
        }

        base.OnStop();
    }

    // ... OnStart omitted
}

public static class Foo
{
    public static async Bar(CancellationToken token)
    {
        while (true)
        {
            try
            {
                token.ThrowIfCancellationRequested();

                var bar = await FooService.GetBar().ConfigureAwait(false);

                // Now token.IsCancellationRequested == true. Why? The above call does not take the token as input.
            }
            catch (OperationCanceledException)
            {
                // ... Handling
            }
        }
    }
}

I've successfully used CancellationTokens once before in another project and I use a similar setup here. The only difference I'm aware of is that this is in an Azure Cloud Service. Any idea why IsCancellationRequested is getting set to true?


Solution

  • It appears OnStop was called while you where awaiting for FooService.GetBar() to complete. Perhaps add some form of logging to see if OnStop is called between the token.ThrowIfCancellationRequested(); and after the var bar = await ... returns to confirm.

    That is what is causing the token to be canceled.

    To solve the problem you need to make sure the overridden Run method does not return till the work is complete.

    public override void Run()
    {
        this.cancellationTokenSource = new CancellationTokenSource();
        this.runTask = Task.Run(() => Foo.Bar(this.cancellationTokenSource.Token), this.cancellationTokenSource.Token);
        this.runTask.Wait(); //You may need a try/catch around it
    }