Reputation: 23224
Given the following code, I would expect the actions to be executed sequentially
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp32
{
class Program
{
static SpinLock Lock = new SpinLock();
static Task Head = Task.CompletedTask;
static async Task Main(string[] args)
{
Console.WriteLine("Hello World!");
CreateTask(1);
CreateTask(2);
CreateTask(3);
CreateTask(4);
await Task.Delay(1000); // Half way through executing
CreateTask(5);
CreateTask(6);
CreateTask(7);
CreateTask(8);
await Task.Delay(5000);
}
static void CreateTask(int i)
{
bool lockTaken = false;
while (!lockTaken)
Lock.Enter(ref lockTaken);
try
{
Head = Head.ContinueWith(_ => DoActionAsync(i));
}
finally
{
Lock.Exit();
}
}
static async Task DoActionAsync(int i)
{
Console.WriteLine(DateTime.UtcNow.ToString("HH:mm:ss") + ": Creating " + i);
await Task.Delay(1000);
Console.WriteLine(DateTime.UtcNow.ToString("HH:mm:ss") + ": Finished " + i);
}
}
}
But the actual output is
Hello World!
09:08:06: Creating 1
09:08:06: Creating 2
09:08:06: Creating 3
09:08:06: Creating 4
09:08:07: Finished 2
09:08:07: Finished 3
09:08:07: Finished 4
09:08:07: Creating 5
09:08:07: Finished 1
09:08:07: Creating 6
09:08:07: Creating 7
09:08:07: Creating 8
09:08:08: Finished 7
09:08:08: Finished 6
09:08:08: Finished 8
09:08:08: Finished 5
Why is 2 finishing before 1 rather than not starting until after 1 has completed? (I expect this is the same cause as them all finishing at the same time).
Upvotes: 0
Views: 1283
Reputation: 46
That is because the tasks are nested. You are creating tasks within tasks without unwrapping them.
try this:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp32
{
class Program
{
static SpinLock Lock = new SpinLock();
static Task Head = Task.CompletedTask;
static async Task Main(string[] args)
{
Console.WriteLine("Hello World!");
CreateTask(1);
CreateTask(2);
CreateTask(3);
CreateTask(4);
await Task.Delay(1000); // Half way through executing
CreateTask(5);
CreateTask(6);
CreateTask(7);
CreateTask(8);
await Task.Delay(5000);
}
static void CreateTask(int i)
{
bool lockTaken = false;
while (!lockTaken)
Lock.Enter(ref lockTaken);
try
{
Head = Head.ContinueWith(_ => DoActionAsync(i)).Unwrap();
}
finally
{
Lock.Exit();
}
}
static async Task DoActionAsync(int i)
{
Console.WriteLine(DateTime.UtcNow.ToString("HH:mm:ss") + ": Creating " + i);
await Task.Delay(1000);
Console.WriteLine(DateTime.UtcNow.ToString("HH:mm:ss") + ": Finished " + i);
}
}
}
note the Unwrap()
The overload you are "abusing" is the general Func<object, T>
one where you assign Task to type T instead of a result type. Normally ContinueWith
uses a synchronous delegate.
.NET thought about this and they have created Unwrap()
, which unwraps the nested task.
Upvotes: 3
Reputation: 36341
.ContinueWith(_ => DoActionAsync(i))
Returns a Task
(or specifically a Task<Task>
) that completes when DoActionAsync
returns. DoActionAsync will return at the first await, thus completing the outer Task
and allowing the next operation to begin.
So it will run each "create" serially, but "Finished" will run asynchronously.
A simple solution would be to schedule all the tasks using a LimitedconcurrencyTaskScheduler instead. Or just create a queue of the operations and have a thread processing the queue in order.
Upvotes: 2