Jonik
Jonik

Reputation: 1249

Task.ContinueWith and async (core 3.1)

My code is similar to this:

    public async Process()
    {
        try
        {
            var records = await this.provider.GetRecordsAsync(); // this method can throws 'DataStoreException' only
    
            foreach(var record in records)
            {
                try
                {
                    this.Do(record); // here is some exception and code goes to catch
                    record.Status = Status.Success;
                    await this.provider.UpdateAsync(record).ContinueWith(
                        task =>
                        {
                            task.Exception?.Handle(e =>
                            {
                                this.logger.LogError(e);
                                return true;
                            });
                        }, TaskContinuationOptions.NotOnRanToCompletion);
                }
                catch(Exception e)
                {
                    record.Status = Status.Error;
/*line 106:*/           await this.provider.UpdateAsync(record).ContinueWith(
                        task =>
                        {
                            task.Exception?.Handle(e =>
                            {
                                this.logger.LogError(e);
                                return true;
                            });
                        }, TaskContinuationOptions.NotOnRanToCompletion);
                }
            }
        }
        catch (DataStoreException e)
        {
            this.logger.LogError(e);
        }
        catch (Exception e)
        {
            // Got TaskCanceledException here
        }
    }

And at commented point I got TaskCanceledException. I suppose the reason are mixed await and ContinueWith, but can some one explain, what happened here? UpdateAsync can throw DataStoreException only. So TaskCanceledException thew by ContinueWith, but there is no cancellation tokens, code do not even reach this point task.Exception?.Handle

Here is the CallStack:

   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at ProjectTest.MyClass.<Process>d__6.MoveNext() in C:\Projects\ProjectTest\MyClass.cs:line 106

If try/catch surrounds this.provider.UpdateAsync(record) instead of ContinueWith everything is good and code works as expected. Does it actually bad practice to mix await and ContinueWith and if so, why?

Upvotes: 1

Views: 519

Answers (1)

StriplingWarrior
StriplingWarrior

Reputation: 156459

The behavior you're seeing can be reproduced much more simply:

await Task.CompletedTask.ContinueWith(task =>
    {
    }, TaskContinuationOptions.NotOnRanToCompletion);

TaskContinuationOptions.NotOnRanToCompletion instructs the ContinueWith method to not execute the callback if the task completes successfully, and instead return a cancelled task.

You would use that option if you have a continuation that you specifically expect to run when an error occurs. For example:

await Task.FromException(new Exception()).ContinueWith(task =>
    {
        Console.WriteLine("Task did not execute to completion");
    }, TaskContinuationOptions.NotOnRanToCompletion);

Since it looks like you expect this.provider.UpdateAsync(record) might succeed, that's probably not the option you want to use.

Upvotes: 1

Related Questions