Martin Sykora
Martin Sykora

Reputation: 435

ContinueWith chaining not working as expected

I have this example code:

    static void Main(string[] args) {
        var t1 = Task.Run(async () => {
            Console.WriteLine("Putting in fake processing 1.");
            await Task.Delay(300);
            Console.WriteLine("Fake processing finished 1. ");
        });
        var t2 = t1.ContinueWith(async (c) => {
            Console.WriteLine("Putting in fake processing 2.");
            await Task.Delay(200);
            Console.WriteLine("Fake processing finished 2.");
        });
        var t3 = t2.ContinueWith(async (c)  => {
            Console.WriteLine("Putting in fake processing 3.");
            await Task.Delay(100);
            Console.WriteLine("Fake processing finished 3.");
        });
        Console.ReadLine();
    }

The console output baffles me:

I am trying to chain the tasks so they execute one after another, what am I doing wrong? And I can't use await, this is just example code, in reality I am queueing incoming tasks (some asynchronous, some not) and want to execute them in the same order they came in but with no parallelism, ContinueWith seemed better than creating a ConcurrentQueue and handling everythning myself, but it just doesn't work...

Upvotes: 5

Views: 998

Answers (2)

Stephen Cleary
Stephen Cleary

Reputation: 457017

in reality I am queueing incoming tasks (some asynchronous, some not) and want to execute them in the same order they came in but with no parallelism

You probably want to use TPL Dataflow for that. Specifically, ActionBlock.

var block = new ActionBlock<object>(async item =>
{
  // Handle synchronous item
  var action = item as Action;
  if (action != null)
    action();

  // Handle asynchronous item
  var func = item as Func<Task>;
  if (func != null)
    await func();
});

// To queue a synchronous item
Action synchronous = () => Thread.Sleep(1000);
block.Post(synchronous);

// To queue an asynchronous item
Func<Task> asynchronous = async () => { await Task.Delay(1000); };
blockPost(asynchronous);

Upvotes: 2

Servy
Servy

Reputation: 203837

Take a look at the type of t2. It's a Task<Task>. t2 will be completed when it finishes starting the task that does the actual work not when that work actually finishes.

The smallest change to your code to get it to work would be to add an unwrap after both your second and third calls to ContinueWith, so that you get out the task that represents the completion of your work.

The more idiomatic solution would be to simply remove the ContinueWith calls entirely and just use await to add continuations to tasks.

Interestingly enough, you would see the same behavior for t1 if you used Task.Factory.StartNew, but Task.Run is specifically designed to work with async lambdas and actually internally unwraps all Action<Task> delegates to return the result of the task returned, rather than a task that represents starting that task, which is why you don't need to unwrap that task.

Upvotes: 9

Related Questions