Search code examples
c#async-awaittaskcompletionsource

TaskCompletionSource usage


What would I receive in result variable if completion source was cancelled?

async void SomeMethod()
{
   .....
   Run();
   var result = await GetResult();
   .....
}

Task<SomeResult> GetResult()
{
    return myCompletionSource.Task;
}

TaskCompletionSource myCompletionSource;

void Run()
{
     myCompletionSource= new TaskCompletionSource();
     TriggerSomeLongLastingLogicWhichWillCallCallBackBelow();

}

void SomeCallback()
{
     if (someCondition)
     {
         myCompletionSource.SetResult(<someResult>);
     }
     else
     {
         myCompletionSource.SetCancelled();
     }
}

I'm not quite sure whether this approach is correct.

  1. In other words is it a good practice to rely on task status rather than creating a wrapper for "someresult" with status variable?
  2. How to handle cancelled task? I'm not a fan of callbacks and don't like solution with ContinueWith, where I can analize task status.

Solution

  • What would I receive in result variable if completion source was cancelled?

    You code will throw an OperationCancelledException when awaiting a cancelled task. So the result variable will never be set.

    You can handle the exception with a try/catch block:

    async Task SomeMethod()
    {
       try
       {
           .....
           Run();
           var result = await GetResult();
       }
       catch(OperationCancelledException)
       {
           // handle cancelled operation
       }
    }
    

    Also, SomeMethod should return a Task as void returning async methods are usually only appropriate for event handlers as they must return void. I blog about it briefly here.

    In general, if you want an operation to be cancelable you pass in a CancellationToken which the operation must check and pass on to other operations it kicks off. So you pass it all the way down the chain and into your callback.

    You can also register a callback with the CancellationToken that cancels the TaskCompletionSource when the token is cancelled so you don't need to do it in your method.

    void Run()
    {   
         var cts = new CancellationTokenSource();
         var myCompletionSource= new TaskCompletionSource();
         cts.Token.Register(() => myCompletionSource.SetCancelled());
    
         TriggerSomeLongLastingLogicWhichWillCallCallBackBelow(cts.Token);         
    }
    
    void SomeCallback(CancellationToken token)
    {       
         // do some work
         ....
    
         token.ThrowIfCancellationRequested();
    
         if (someCondition)
         {
             myCompletionSource.SetResult(<someResult>);
         }
         else
         {
             myCompletionSource.SetException(new Exception("error occcured"));
         }
    }