Search code examples
c#win-universal-apptaskcancellationcancellationtokensource

Cancelling an entire task when a called method does not return


I have a method that registers a background task that looks like this:

//snippet from task builder method
        try
        {
            cancellationTokenSource.CancelAfter(10000);
            btr = Task.Run(() => registerTask(builder, btr,cancellationTokenSource.Token), cancellationTokenSource.Token).Result;
        }
        catch (OperationCanceledException) // something went wrong
        {
            return null;
        }



private BackgroundTaskRegistration registerTask(BackgroundTaskBuilder builder, BackgroundTaskRegistration btr, CancellationToken token)
{
    CancellationTokenSource newToken = new CancellationTokenSource();
    Task cancelledCheck = Task.Run(() =>
    {
        while (true)
        {
            if (token.IsCancellationRequested)
            {
                newToken.Cancel();
                token.ThrowIfCancellationRequested();
            }
        }
    }, newToken.Token);
    btr = Task.Run(()=> builder.Register(),token).Result;
    return btr;
}

My issue is that sometimes the builder.Register() method does not return anything. It's probably a Windows bug of some sort; the Register() method never finishes internally. Indeed, after 10 seconds, the token.ThrowIfCancellationRequested() method is called, but it does not throw to the try-catch statement where it's called. Initially I was calling builder.Register() directly without a Task.Run() but it didn't work, and neither does this.

However, if I replace btr = Task.Run(() =>... with a Task.Delay(ms) instead, where ms > 10000, my intended effect happens.

Am I doing something wrong? Or is there a better way to do this? Basically I just need code that will make the registerTask() method return null when builder.Register() does not finish after a few seconds.


Solution

  • Replacing the code with something like this worked for me:

        btr = null;
        cancellationTokenSource.CancelAfter(10000);
        Task registerTask = Task.Factory.StartNew(() =>
        {
          btr = builder.Register();
        });
       Task cancellationTask = Task.Factory.StartNew(() =>
       {
         while (true)
         {
           if (cancellationTokenSource.Token.IsCancellationRequested) break;
         }
         }, cancellationTokenSource.Token);
       Task[] tasks = new Task[2] { cancellationTask, registerTask };
       Task.WaitAny(tasks);
    

    Instead of handling the error, getting a cancellation request will trigger one of the tasks to end, and when it ends I return the task registration variable whether it's null or not.