locked
good way to cancel async method RRS feed

  • Question

  • I actively use async methods in my project.. The main async method "async Task Run()" calls some other via await keyword etc. Methods implement some logic based on events to be fired from thread №2. So my application is not multi-thread but event-based and I activelly use event subscription / unsubscription and TaskCompletionSource..

    My question is: how to implement logic of manual stopping of root async method Run?

    Is it possible to "bind" cancelation to only one probably most outer Task wich is returning from method Run() and don't hard-code cancelation logic everywhere? Keep in mind that many my async methods have event unsubscription code which should be called in case of Task completion or cancellation.


    Sergey.
    Monday, January 10, 2011 2:30 PM

Answers

  • re: "What is better to do on cancellation in async leave methods: call "tcs.SetCanceled();" or throw OperationCanceledException?"

    In a leaf asynchronous method, you typically have some low-level mechanism for asynchrony that you want to represent as a task.  In that case, you likely need to do so manually using TaskCompletionSource, in which case you need to manually complete it using the TCS's {Try}Set* methods.  If you instead have the ability at that level to write an async method using the language support, the language/library is handling the creation and completion of the task for you, and throwing an OperationCanceledException is your mechanism for getting that task into the Canceled state.  As such, you won't typically find yourself in a situation where you can either use SetCanceled or throw... which you pick is based on the kind of method you're writing, and whether you creating the task yourself (with TaskCompletionSource) or whether the compiler did it for you (with async methods).

    re: "As I understood from the TAP document cancelling of "child" Task will throw an exception in awaiting code of parent Task and it can be propageted to outer Task"

    If I'm understanding you correctly, that's the basic idea (though I've opted not to use that parent/child nomenclature, as we use that nomenclature for another set of features in TPL that's orthogonal to this asynchrony support).  When you await a task, if that task ends in the canceled state, an OperationCanceledException will be thrown.  And when that exception escapes from the async method it was thrown in, the task that was returned from that async method will be canceled.  If someone somewhere else is awaiting that task, an OCE will be thrown, and their task will be canceled, and so on up the chain.  So, if you pass a cancellation token to a method whose returned task you then await, the cancellation is in effect handled automatically.

     

    Thursday, January 13, 2011 8:24 AM
    Moderator

All replies

  • Hi Sergey-

    If you have unsubscription code that must be called in the case of cancellation, then you need a way to register that per-operation cancellation code, which necessarily means doing some kind of cancellation registration per operation.  A good way to do this is simply to have all of your operations that are cancelable accept as a parameter a CancellationToken.  The user can just pass a CancellationToken (potentially CancellationToken.None) to your root method, and that root method can simply pass that same token instance to all methods it calls, and so on.  If the user requests cancellation through the token, everyone that's received a copy of the token will be notified of the cancellation request, and can execute their unsubscription code.  CancellationToken provides the Register(Action) method for exactly this purpose, allowing you to register a callback that will be invoked when cancellation is requested.

    Monday, January 10, 2011 4:46 PM
    Moderator
  • Hi Sergey-

    If you have unsubscription code that must be called in the case of cancellation, then you need a way to register that per-operation cancellation code, which necessarily means doing some kind of cancellation registration per operation.  A good way to do this is simply to have all of your operations that are cancelable accept as a parameter a CancellationToken.  The user can just pass a CancellationToken (potentially CancellationToken.None) to your root method, and that root method can simply pass that same token instance to all methods it calls, and so on.  If the user requests cancellation through the token, everyone that's received a copy of the token will be notified of the cancellation request, and can execute their unsubscription code.  CancellationToken provides the Register(Action) method for exactly this purpose, allowing you to register a callback that will be invoked when cancellation is requested.

    Hi Stephen!! Thank you!

    Do you mean something like this?

     

     

    class Processor
    
    {
    
    public event Action NewEvent;
    
    
    
    public async Task Run(CancelationTokenSource cts)
    
    {
     // some async helper method
      await CanStart(cts.Token);
    
    
    
      while(!cts.Token.IsCancellationRequested)
    
      {
       // some other async method
         await DoPieceOfWork(cts);
    
      }
    
    }
    
    
    // example of cancellation Token processing
    private Task DoPieceOfWork(CancelationToken ct)
    
    {
    
      Action process = null;
    
    
    
      Action init = () =>
    
      {
    
        this.NewEvent += process;
    
    
    
        // begin some action here
    
      }
    
    
    
      Action clear = () =>
    
      {
    
        this.NewEvent -= process;
    
      }
    
    
    
      ct.Register(clear);
    
    
    
      var tcs = new TaskCompletionSource<object>();
    
    
    
      process = () =>
    
      {
    
        // some processing here
    
        
    
        if(/* work is completed */)
    
        {
    
          clear();
    
          tcs.SetResult(null);
    
        }
    
      }
    
      init();
      return tcs.Task;
    
    }
    
    }

    Will CancellationTokenSource throw some exception in "await" code when Cancel called?

     

    for example how will work this if in some other method calls cts.Cancel()?

     

     

    await processor.Run(cts);
    
    Console.WriteLine("ALready is not running") ;
    
    

     

     

     

     

     


    Sergey.
    Monday, January 10, 2011 5:47 PM
  • Hi Sergey-

    If you have unsubscription code that must be called in the case of cancellation, then you need a way to register that per-operation cancellation code, which necessarily means doing some kind of cancellation registration per operation.  A good way to do this is simply to have all of your operations that are cancelable accept as a parameter a CancellationToken.  The user can just pass a CancellationToken (potentially CancellationToken.None) to your root method, and that root method can simply pass that same token instance to all methods it calls, and so on.  If the user requests cancellation through the token, everyone that's received a copy of the token will be notified of the cancellation request, and can execute their unsubscription code.  CancellationToken provides the Register(Action) method for exactly this purpose, allowing you to register a callback that will be invoked when cancellation is requested.

    Hi Stephen!! Thank you!

    Do you mean something like this?

     

     

    class Processor
    
    {
    
    public event Action NewEvent;
    
    
    
    public async Task Run(CancelationTokenSource cts)
    
    {
     // some async helper method
      await CanStart(cts.Token);
    
    
    
      while(!cts.Token.IsCancellationRequested)
    
      {
      // some other async method
         await DoPieceOfWork(cts);
    
      }
    
    }
    
    
    // example of cancellation Token processing
    private Task DoPieceOfWork(CancelationToken ct)
    
    {
    
      Action process = null;
    
    
    
      Action init = () =>
    
      {
    
        this.NewEvent += process;
    
    
    
        // begin some action here
    
      }
    
    
    
      Action clear = () =>
    
      {
    
        this.NewEvent -= process;
    
      }
    
    
    
      ct.Register(clear);
    
    
    
      var tcs = new TaskCompletionSource<object>();
    
    
    
      process = () =>
    
      {
    
        // some processing here
    
        
    
        if(/* work is completed */)
    
        {
    
          clear();
    
          tcs.SetResult(null);
    
        }
    
      }
    
      init();
      return tcs.Task;
    
    }
    
    }

    Will CancellationTokenSource throw some exception in "await" code when Cancel called?

     

    for example how will work this if in some other method calls cts.Cancel()?

     

     

    await processor.Run(cts);
    
    Console.WriteLine("ALready is not running") ;
    
    

     

     

     

     

     


    Sergey.

     

    Stephen,

    I have found that a also should call SetCanceled() on TaskCompletionSource instance used in async methods .. Am I right?

    (without this call just event unsubscription delegate is called (registsred via Register method) and nothing is done with Task (code after await processor.Run() is not called and no Exception is thrown)).


    Sergey.
    Monday, January 10, 2011 6:52 PM
  • Hi Sergey-

    That's the basic idea.

    Regarding SetCanceled, if you throw an OperationCanceledException from an async method, it'll translate that into cancellation of the returned task.  So, for example, you could rewrite your top-level Run as:

    public async Task Run(CancelationTokenSource cts)
    {
        while(true)
        {
            cts.Token.ThrowIfCancellationRequested(); // equivalent to: if (cts.Token.IsCancellationRequested) throw new OperationCanceledException(cts.Token);
            await DoPieceOfWork(cts);
        }
    }

    Also, with ct.Register, it's a good idea to call Dispose on the returned CancellationTokenRegistration when you're done with it. That said, since you're going to do the unregistration code when you're done, regardless of whether you're done due to cancellation or successful completion, the code can probably be written a bit more simply. For example, your DoPieceOfWorkMethod could look something like:

    private Task DoPieceOfWork(CancelationToken ct)
    {
        var tcs = new TaskCompletionSource<object>();
        var ctr = default(CancellationTokenRegistration);
        EventHandler process = null;

        process = delegate
        {
            ctr.Dispose();
            this.NewEvent -= process;
            if (ct.IsCancellationRequested) tcs.SetCanceled();
            else tcs.SetResult(null); /* work completed */
        }
        ctr = ct.Register(process);
        this.NewEvent += process;

        ... // begin some action here;
        return tcs.Task;
    }

    Here, you're invoking the process function when either cancellation is requested or when your event is raised.  Either way, you undo the registration on the cancellation token and you undo the registration with the event.  Then if the work is completed you call SetResult, or if cancellation has been requested, you call SetCanceled.  I may not have captured here exactly what your business logic is, but that should give you the basic idea.

    Usage of TaskCompletionSource like this should really only be needed at the leaves of your APIs, where you're translating other asynchronous operations into tasks.  When you're just composing together other APIs that return tasks, it becomes more like your Run method that is an async method (with the compiler producing the task for you).

     

    Monday, January 10, 2011 8:48 PM
    Moderator
  • Hi Sergey-

    That's the basic idea.

    Regarding SetCanceled, if you throw an OperationCanceledException from an async method, it'll translate that into cancellation of the returned task.  So, for example, you could rewrite your top-level Run as:

    public async Task Run(CancelationTokenSource cts)
    {
        while(true)
        {
            cts.Token.ThrowIfCancellationRequested(); // equivalent to: if (cts.Token.IsCancellationRequested) throw new OperationCanceledException(cts.Token);
            await DoPieceOfWork(cts);
        }
    }

    Also, with ct.Register, it's a good idea to call Dispose on the returned CancellationTokenRegistration when you're done with it. That said, since you're going to do the unregistration code when you're done, regardless of whether you're done due to cancellation or successful completion, the code can probably be written a bit more simply. For example, your DoPieceOfWorkMethod could look something like:

    private Task DoPieceOfWork(CancelationToken ct)
    {
        var tcs = new TaskCompletionSource<object>();
        var ctr = default(CancellationTokenRegistration);
        EventHandler process = null;

        process = delegate
        {
            ctr.Dispose();
            this.NewEvent -= process;
            if (ct.IsCancellationRequested) tcs.SetCanceled();
            else tcs.SetResult(null); /* work completed */
        }
        ctr = ct.Register(process);
        this.NewEvent += process;

        ... // begin some action here;
        return tcs.Task;
    }

    Here, you're invoking the process function when either cancellation is requested or when your event is raised.  Either way, you undo the registration on the cancellation token and you undo the registration with the event.  Then if the work is completed you call SetResult, or if cancellation has been requested, you call SetCanceled.  I may not have captured here exactly what your business logic is, but that should give you the basic idea.

    Usage of TaskCompletionSource like this should really only be needed at the leaves of your APIs, where you're translating other asynchronous operations into tasks.  When you're just composing together other APIs that return tasks, it becomes more like your Run method that is an async method (with the compiler producing the task for you).

     

    Hi Stephen,

    thanks you for explanation!!! I also have found your document with TAP description.. Both are very helpfull.

    What do you think? What is better to do on cancellation in async leave methods: call "tcs.SetCanceled();" or throw OperationCanceledException?

    Other question.May I understand correctly that non-leaves async methods (they await for other async methods) probably don't need special processing of cancellation (if they didn't subscribe on events), just passing CancellationToken to methods they are awaiting? As I understood from the TAP document cancelling of "child" Task will throw an exception in awaiting code of parent Task and it can be propageted to outer Task.

     

    Sergey.


    Sergey.
    Tuesday, January 11, 2011 9:40 AM
  • re: "What is better to do on cancellation in async leave methods: call "tcs.SetCanceled();" or throw OperationCanceledException?"

    In a leaf asynchronous method, you typically have some low-level mechanism for asynchrony that you want to represent as a task.  In that case, you likely need to do so manually using TaskCompletionSource, in which case you need to manually complete it using the TCS's {Try}Set* methods.  If you instead have the ability at that level to write an async method using the language support, the language/library is handling the creation and completion of the task for you, and throwing an OperationCanceledException is your mechanism for getting that task into the Canceled state.  As such, you won't typically find yourself in a situation where you can either use SetCanceled or throw... which you pick is based on the kind of method you're writing, and whether you creating the task yourself (with TaskCompletionSource) or whether the compiler did it for you (with async methods).

    re: "As I understood from the TAP document cancelling of "child" Task will throw an exception in awaiting code of parent Task and it can be propageted to outer Task"

    If I'm understanding you correctly, that's the basic idea (though I've opted not to use that parent/child nomenclature, as we use that nomenclature for another set of features in TPL that's orthogonal to this asynchrony support).  When you await a task, if that task ends in the canceled state, an OperationCanceledException will be thrown.  And when that exception escapes from the async method it was thrown in, the task that was returned from that async method will be canceled.  If someone somewhere else is awaiting that task, an OCE will be thrown, and their task will be canceled, and so on up the chain.  So, if you pass a cancellation token to a method whose returned task you then await, the cancellation is in effect handled automatically.

     

    Thursday, January 13, 2011 8:24 AM
    Moderator
  • re: "What is better to do on cancellation in async leave methods: call "tcs.SetCanceled();" or throw OperationCanceledException?"

    In a leaf asynchronous method, you typically have some low-level mechanism for asynchrony that you want to represent as a task.  In that case, you likely need to do so manually using TaskCompletionSource, in which case you need to manually complete it using the TCS's {Try}Set* methods.  If you instead have the ability at that level to write an async method using the language support, the language/library is handling the creation and completion of the task for you, and throwing an OperationCanceledException is your mechanism for getting that task into the Canceled state.  As such, you won't typically find yourself in a situation where you can either use SetCanceled or throw... which you pick is based on the kind of method you're writing, and whether you creating the task yourself (with TaskCompletionSource) or whether the compiler did it for you (with async methods).

    re: "As I understood from the TAP document cancelling of "child" Task will throw an exception in awaiting code of parent Task and it can be propageted to outer Task"

    If I'm understanding you correctly, that's the basic idea (though I've opted not to use that parent/child nomenclature, as we use that nomenclature for another set of features in TPL that's orthogonal to this asynchrony support).  When you await a task, if that task ends in the canceled state, an OperationCanceledException will be thrown.  And when that exception escapes from the async method it was thrown in, the task that was returned from that async method will be canceled.  If someone somewhere else is awaiting that task, an OCE will be thrown, and their task will be canceled, and so on up the chain.  So, if you pass a cancellation token to a method whose returned task you then await, the cancellation is in effect handled automatically.

     

    Hi Stephen,

    thank you!

    One more detail.. I see overloads of TaskEx.Run with CancellationToken param. Does it provide just checking of CancellationToken on the beginning of execution? So can I manually throw OCE in both overloads of methods: with and without CT param?


    Sergey.
    Thursday, January 13, 2011 11:07 AM