1

As part of a TPL project, I'm chaining Tasks when they complete. The behavior of ContinueWith() is unintuitive to me. Consider the following snippet:

private void Execute()
{
    var flow = Task.CompletedTask       //Task.CompletedTask for the sake of context
       .ContinueWith(async _ =>    
       {
           var t1 = Task.Run(LongRunningTask1());
           await t1;
       })
       .ContinueWith(async t =>
       {
           // STUFF
       });

    flow.Wait();
}

private Func<Task> LongRunningTask1()
{
    return new Func<Task>(async () =>
    {
        Console.WriteLine("Task 1 Starting");
        await Task.Delay(5000);
        Console.WriteLine("Task 1 Complete");
    });
}

This 'falls through' to // STUFF without awaiting t1.

If I replace await t1 with t1.Wait() it does behave as expected.

I'm really looking at understanding the difference in behavior here.

Wouter Van Ranst
  • 521
  • 4
  • 14
  • I did post an answer about your LongRunningTask1() returning a Func rather than actually invoking it. I realise now that Task.Run has an overload that takes a Func and should run it, so my answer is most likely wrong (hence why I deleted it) -> however it might be worth looking into, see what type t1 is, is a Task or a Func. Also try manually awaiting invoking the func yourself `Task.Run(() => LongRunningTask1()());` - again I'm probably wrong but maybe worth looking into – Dave Feb 06 '21 at 08:32
  • 2
    This is just a variation on all the other "I didn't expect my async method to actually return when it reached the `await`" questions. In your example, the first `ContinueWith()` lambda is considered _done_ when it reaches the `await`, because that's exactly what `await` does...it cause the method to return, and the method returning is all `ContinueWith()` cares about in terms of completion. Note that you really should avoid using `ContinueWith()` altogether. It has some surprising behaviors, as compared to correctly composing async results with `await` only. – Peter Duniho Feb 06 '21 at 08:44
  • 2
    You can use [`Unwrap`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskextensions.unwrap?view=net-5.0) on the first continuation but AFAIK after `async-await` was introduced it is usually not recommended to use `ContinueWith` in modern C# code in most cases. Also it is heavily recommended against blocking on asynchronous code (i.e. `flow.Wait();`) – Guru Stron Feb 06 '21 at 08:48
  • t1 is of type `Task`. Replacing `LongRunningTask1()` by `async () => { await Task.Delay(1000); });` has the same behavior. – Wouter Van Ranst Feb 06 '21 at 09:10
  • @PeterDuniho , wdym "it causes the method to return" -- according to the spec "The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes." - and the task here is not completed, hence we're not ready for ContinueWith imho? – Wouter Van Ranst Feb 06 '21 at 09:13
  • 1
    @WouterVanRanst yes it does, but if the method calling the `async` one does not `await` the returned `Task` the calling method continues the execution. – Guru Stron Feb 06 '21 at 09:20
  • @WouterVanRanst see what `async` and `async-await` are actually turned into by compiler – Guru Stron Feb 06 '21 at 09:22
  • 1
    _"The await operator suspends evaluation of the enclosing async method"_ -- that part of the spec is discussing the _semantics_ of the `await` operator. But you need to keep in mind what _"suspends evaluation"_ actually means. Specifically: it means that the method **returns**, with a `Task` object to the caller, that the caller can then use to wait for the promised result from the method. But `ContinueWith()` doesn't concern itself with that; it receives the returned `Task` object and considers the continuation _complete_ at that point. The `Task` object is simply the _result_ of the ... – Peter Duniho Feb 06 '21 at 19:05
  • 1
    ... continuation, as far as `ContinueWith()` is concerned. You have to add code yourself, e.g. in the _next_ continuation, to wait for the result of the `Task`, if you want any waiting to happen. All that said, keep in mind my earlier comment: you _really_ should not be using `ContinueWith()` at all. Just use `await`. – Peter Duniho Feb 06 '21 at 19:05
  • @WouterVanRanst: [`ContinueWith` is a low-level method with dangerous default behavior](https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html). Replace `ContinueWith` with `await` and your code will become clearer. – Stephen Cleary Feb 07 '21 at 02:47

0 Answers0