如何让 Task.ContinueWith 依次执行异步方法?

How can I get Task.ContinueWith to execute async methods in sequence?

给定以下代码,我希望操作按顺序执行

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);
        }
    }
}

但实际输出是

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

为什么 2 在 1 之前完成,而不是在 1 完成之后才开始? (我想这是同一个原因,因为他们都同时完成了)。

.ContinueWith(_ => DoActionAsync(i)) Returns Task(或者特别是 Task<Task>)在 DoActionAsync return 时完成。 DoActionAsync 将在第一次等待时 return,从而完成外部 Task 并允许下一个操作开始。

因此它将 运行 每个“创建”顺序地进行,但是“完成”将 运行 异步进行。

一个简单的解决方案是使用 LimitedconcurrencyTaskScheduler 来安排所有任务。或者只是创建一个操作队列并让一个线程按顺序处理队列。

那是因为任务是嵌套的。您正在任务中创建任务而不展开它们。

试试这个:

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);
        }
    }
}

注意 Unwrap() 您“滥用”的重载是一般的 Func<object, T> 重载,您将 Task 分配给类型 T 而不是结果类型。通常 ContinueWith 使用同步委托。 .NET 考虑到了这一点,他们创建了 Unwrap(),它解包了嵌套任务。