如何按顺序而不是并行等待 C# 任务?

Maj*_*jor 2 .net c# asynchronous task async-await

我有一组异步测试,它们在外部硬件上运行。

我可以按顺序运行它们,但因为所有这些测试都有副作用,所以我希望能够重新排列它们并一遍又一遍地运行它们。

当我将它们放入列表中并尝试等待每个之后,它们全部并行运行,而不是 1 个 1 个运行。

我希望能够打乱列表,然后按顺序运行它们。

如何按顺序运行测试 ( List<Task<bool>),同时将它们保留在列表中以便我可以随机播放列表?

private async void RunVisca()
{
    Ptz ptz = new Ptz(SelectedComPort, (byte)(1), SelectedBaudRate, Node.DeviceType.PTZ);

    UpdateResults("VISCA TEST START\n\n");

    // Method 1: Correct - Runs in order
    await VTestDriveClosedLoop(ptz, true);
    await VTestDriveClosedLoop(ptz, false);
    await VTestLimitsCl(ptz, 125, 20);
    await VTestLimitsOl(ptz, 125, 20);

    // Method 2: Incorrect - Runs in parallel
    List<Task<bool>> tests = new List<Task<bool>>();
    tests.Add(VTestDriveClosedLoop(ptz, true));
    tests.Add(VTestDriveClosedLoop(ptz, false));
    tests.Add(VTestLimitsCl(ptz, 125, 20));
    tests.Add(VTestLimitsOl(ptz, 125, 20));

    // Tasks all run in parallel here - should they not run in order?
    foreach(Task<bool> test in tests)
    {
        _ = await test.ConfigureAwait(true);
    }

    UpdateResults("\nTEST COMPLETE\n\n");
}
Run Code Online (Sandbox Code Playgroud)

Erm*_*ary 9

基于任务的异步模式 (TAP) 模式意味着调用async返回 a 的方法Task将返回一个热任务

处于热状态的ATask已经启动。这可以通过检查Task.Status属性来证明,但永远不会TaskStatus.Created

冷任务将具有&Task.Status属性,仅在实例上调用TaskStatus.Created时才开始运行。Start()

创建冷任务的唯一方法是使用Task/Task<T>各自的公共构造函数。


由于您的任务是通过异步方法返回的,因此当它们返回给您时它们已经在运行。

要按顺序运行它们而不是并行运行它们,除非前一个任务已完成,否则不得相继创建它们。为了确保这一点,您必须单独使用await它们。

这导致方法 1 成为按顺序运行热任务而不是并行运行热任务的正确方法。

await VTestDriveClosedLoop(ptz, true);
await VTestDriveClosedLoop(ptz, false);
await VTestLimitsCl(ptz, 125, 20);
await VTestLimitsOl(ptz, 125, 20);
Run Code Online (Sandbox Code Playgroud)

方法 2/3 是不正确的,因为它将并行运行您的任务。

您将从方法返回的热门任务添加async到列表中,而不等待它们。在您继续将下一个任务添加到列表中之前,它们已经开始运行。

第一个任务不会等待,因此第二个任务将立即开始,无论任务 1 是否完成。

不等待第二个任务,因此第三个任务等。

使用方法 2/3,它们将始终并行运行。

tests.Add(VTestDriveClosedLoop(ptz, true));
tests.Add(VTestDriveClosedLoop(ptz, false));
Run Code Online (Sandbox Code Playgroud)

真正的问题是如何按顺序运行冷任务,以便我们可以将它们存储在列表中,同时利用延迟执行,直到我们循环列表为止。

解决方案是存储委托,这将触发以下任务构造函数:

public Task (Func<TResult> function);

这将创建冷任务,让您在调用Task时运行Func<Task<bool>>

var tests = new List<Func<Task<bool>>>();
tests.Add(() => VTestDriveClosedLoop(ptz, true));
tests.Add(() => VTestDriveClosedLoop(ptz, false));
tests.Add(() => VTestLimitsCl(ptz, 125, 20));
tests.Add(() => VTestLimitsOl(ptz, 125, 20));
    
foreach (var test in tests) {
     await test();
}
Run Code Online (Sandbox Code Playgroud)

下面的演示程序将清楚地显示顺序运行与并行运行的任务。

class Program
{
    private static async Task Main(string[] args)
    {
        // 1... 2... 3... 4...
        var tasksThatRunInOrder = new List<Func<Task<bool>>>();
        tasksThatRunInOrder.Add(() => Test("1"));
        tasksThatRunInOrder.Add(() => Test("2"));
        tasksThatRunInOrder.Add(() => Test("3"));
        tasksThatRunInOrder.Add(() => Test("4"));

        foreach (var test in tasksThatRunInOrder)
            await test();

        // 1, 2, 3, 4
        var testsThatRunInParallel = new List<Task<bool>>();
        testsThatRunInParallel.Add(Test("1"));
        testsThatRunInParallel.Add(Test("2"));
        testsThatRunInParallel.Add(Test("3"));
        testsThatRunInParallel.Add(Test("4"));

        foreach (var test in testsThatRunInParallel)
            await test;
    }

    private static async Task<bool> Test(string x)
    {
        Console.WriteLine(x);
        await Task.Delay(1000);
        return true;
    }
}
Run Code Online (Sandbox Code Playgroud)