Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Task.ContinueWith not working how I expected

Tags:

c#

task

Consider the following code. I am starting with a task that does nothing, and then using ContinueWith() to start 10 calls to a method that increments a counter.

When I run this program, it prints "0", indicating that the increment() method hasn't been called at all. I was expecting it to be called 10 times, since that's how many times I called ContinueWith().

If I uncomment the "Thread.Sleep(20)" line, then it prints "10" as expected.

This happens in either release or debug mode. My system is a core 2 quad with hyperthreading (8 logical cores) running Windows 7 x64.

I assume I have some kind of fundamental misunderstanding about how Task.ContinueWith() works....

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication4
{
    class Program
    {
        static void Main()
        {
            using (var task = Task.Factory.StartNew(()=>{}))
            {
                for (int i = 0; i < 10; ++i)
                {
                    task.ContinueWith(_=> increment());
                    // Thread.Sleep(20);  // Uncomment to print 10 instead of 0.
                }

                task.Wait();
            }

            // This prints 0 UNLESS you uncomment the sleep above.
            Console.WriteLine(counter); 
        }

        static void increment()
        {
            Interlocked.Increment(ref counter);
        }

        private static int counter;
    }
}

Can anyone shed any light on what's going on here?

like image 661
Matthew Watson Avatar asked Aug 09 '11 14:08

Matthew Watson


People also ask

What does calling Task ContinueWith() do?

ContinueWith(Action<Task,Object>, Object, TaskScheduler)Creates a continuation that receives caller-supplied state information and executes asynchronously when the target Task completes.

What is ContinueWith c#?

The ContinueWith function is a method available on the task that allows executing code after the task has finished execution. In simple words it allows continuation. Things to note here is that ContinueWith also returns one Task. That means you can attach ContinueWith one task returned by this method.


3 Answers

The reason is simple: You wait on the task that is already finished. What you really want is to wait for the ten tasks you created in the loop:

var tasks = new List<Task>();
for (int i = 0; i < 10; ++i)
{
    tasks.Add(task.ContinueWith(_=> increment()));
}

Task.WaitAll(tasks.ToArray());
like image 116
Daniel Hilgarth Avatar answered Sep 20 '22 22:09

Daniel Hilgarth


Sure. You're printing out the value when the initial task has completed - you're not waiting for the continuation to occur. In other words, when the initial task completes, 11 things happen:

  • 10 new tasks start executing, each incrementing the counter
  • You print out the counter

You could effectively chain all these together if you want:

task = task.ContinueWith(_=> increment());

Then when the original task finishes, one incrementer will fire. When that finishes, the next incrementer will fire. When that finishes [...]. And when the final incrementer has finished, your Wait call will return.

like image 29
Jon Skeet Avatar answered Sep 21 '22 22:09

Jon Skeet


If you change the main body as follows:

        using (var task = Task.Factory.StartNew(() => { }))
        {
            var t = task;
            for (int i = 0; i < 10; ++i)
            {
               t = t.ContinueWith(_ => increment());
                // Thread.Sleep(20);  // Uncomment to print 10 instead of 0.
            }

            t.Wait();
        }

You will get 10 without sleeping. The main change is t = t.ContinueWith(...) and t is necessary because you're not allowed to change the using() variable.

like image 26
Henk Holterman Avatar answered Sep 21 '22 22:09

Henk Holterman