oelsardine
oelsardine

Reputation: 96

Task.ContinueWith seems to be called more often than there are actual tasks

First, this is from something much bigger and yes, I could completely avoid this using await under normal/other circumstances. For anyone interested, I'll explain below.

To track how many tasks I still have left before my program continues, I've built the following:

A counter:

private static int counter;

Some method:

public static void Test()
{
    List<Task> tasks = new List<Task>();
    for (int i = 0; i < 10000; i++)
    {
        TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
        var task = DoTaskWork();
        task.ContinueWith(t => // After DoTaskWork
        {
            // [...] Use t's Result
            counter--; // Decrease counter
            tcs.SetResult(null); // Finish the task that the UI or whatever is waiting for
        });
        tasks.Add(tcs.Task); // Store tasks to wait for
    }
    Task.WaitAll(tasks.ToArray()); // Wait for all tasks that actually only finish in the ContinueWith
    Console.WriteLine(counter);
}

My super heavy work to do:

private static Task DoTaskWork()
{
    counter++; // Increase counter
    return Task.Delay(500);
}

Now, interestingly I do not receive the number 0 at the end when looking at counter. Instead, the number varies with each execution. Why is this? I tried various tests, but cannot find the reason for the irregularity. With the TaskCompletionSource I believed this to be reliable. Thanks.


Now, for anyone that is interested in why I do this:

I need to create loads of tasks without starting them. For this I need to use the Task constructor (one of its rare use cases). Its disadvantage to Task.Run() is that it cannot handle anything with await and that it needs a return type from the Task to properly run (hence the null as result). Therefore, I need a way around that. Other ideas welcome...

Upvotes: 0

Views: 143

Answers (2)

Peter Bons
Peter Bons

Reputation: 29780

I need to create loads of tasks without starting them ... Therefore, I need a way around that. Other ideas welcome...

So, if I read it correct you want to build a list of tasks without actually run them on creation. You could do that by building a list of Func<Task> objects you invoke when required:

async Task Main()
{
    // Create list of work to do later
    var tasks = new List<Func<Task>>();

    // Schedule some work
    tasks.Add(() => DoTaskWork(1));
    tasks.Add(() => DoTaskWork(2));

    // Wait for user input before doing work to demonstrate they are not started right away
    Console.ReadLine();

    // Execute and wait for the completion of the work to be done
    await Task.WhenAll(tasks.Select(t => t.Invoke()));
    Console.WriteLine("Ready");
}

public async Task DoTaskWork(int taskNr)
{
    await Task.Delay(100);
    Console.WriteLine(taskNr);
}

This will work, even if you use Task.Run like this:

public Task DoTaskWork(int taskNr)
{
    return Task.Run(() =>
    {
        Thread.Sleep(100); Console.WriteLine(taskNr);
    });
}

It this is not want you want can you elaborate more about the tasks you want to create?

Upvotes: 0

oelsardine
oelsardine

Reputation: 96

Well. I am stupid. Just 5 minutes in, I realize that.

I just did the same while locking a helper object before changing the counter in any way and now it works...

private static int counter;
private static object locker = new object();
// [...]
task.ContinueWith(t =>
{
    lock(locker)
        counter--;
    tcs.SetResult(null);
});
// [...]
private static Task DoTaskWork()
{
    lock (locker)
        counter++;
    return Task.Delay(500);
}

Upvotes: 2

Related Questions