简体   繁体   English

如何使用 async/await 实现具有两个等待点的任务

[英]How to implement tasks with two awaiting points, using async/await

I have some asynchronous operations that consist of two distinct stages.我有一些由两个不同阶段组成的异步操作。 Initially I want to await them until the completion of their first stage, and later await them until their final completion.最初我想await他们直到他们的第一阶段完成,然后await他们直到他们最终完成。 Here is a simplified version of these operations:以下是这些操作的简化版本:

async Task<string> TwoStagesAsync()
{
    Console.WriteLine($"Stage 1 Started");
    await Task.Delay(1000); // Simulate an I/O operation
    bool resultOfStage1 = true;
    Console.WriteLine($"Stage 1 Finished");
    if (!resultOfStage1) return null;
    /* Stage separator */
    Console.WriteLine($"Stage 2 Started");
    await Task.Delay(1000); // Simulate an I/O operation
    Console.WriteLine($"Stage 2 Finished");
    return "Hello!";
}

To achieve this requirement I had the idea of representing these two-stage operations as nested tasks: Task<Task<string>> .为了达到这个要求,我想到将这些两阶段操作表示为嵌套任务: Task<Task<string>> This would allow me to await initially the outer task, and later await the result of the outer task, which would be the inner task.这将允许我最初await外部任务,然后await外部任务的结果,这将是内部任务。 This is my currently best attempt to implement this idea:这是我目前实现这个想法的最佳尝试:

async Task<Task<string>> TwoStagesNestedAsync_A() // Problematic
{
    Console.WriteLine($"Stage 1 Started");
    await Task.Delay(1000); // Simulate an I/O operation
    bool resultOfStage1 = true;
    Console.WriteLine($"Stage 1 Finished");
    if (!resultOfStage1) return Task.FromResult((string)null);
    /* Stage separator */
    return Task.Run(async () =>
    {
        Console.WriteLine($"Stage 2 Started");
        await Task.Delay(1000); // Simulate an I/O operation
        Console.WriteLine($"Stage 2 Finished");
        return "Hello!";
    });
}

What I like to this solution is that it works and it is quite readable, since it doesn't require any special synchronization primitives like SemaphoreSlim or TaskCompletionSource .我喜欢这个解决方案的地方是它可以工作并且可读性很强,因为它不需要任何特殊的同步原语,如SemaphoreSlimTaskCompletionSource What I don't like is that the second stage is executed in the ThreadPool context instead of the initial SynchronizationContext .我不喜欢的是第二阶段在ThreadPool上下文中执行,而不是在初始SynchronizationContext中执行。 Is there any way to make it use the current SynchronizationContext from start to finish, without complicating it too much?有没有办法让它从头到尾使用当前的SynchronizationContext ,而不会使它变得太复杂?

I should include one more of my failed attempts.我应该再包括一次我失败的尝试。 Replacing the Task.Run with a local async function doesn't work, because for some reason the line Console.WriteLine($"Stage 2 Started") is executed as part of the first stage, instead of the second stage.本地异步 function替换Task.Run不起作用,因为出于某种原因, Console.WriteLine($"Stage 2 Started")行作为第一阶段的一部分执行,而不是第二阶段。

async Task<Task<string>> TwoStagesNestedAsync_B() // Problematic
{
    Console.WriteLine($"Stage 1 Started");
    await Task.Delay(1000); // Simulate an I/O operation
    bool resultOfStage1 = true;
    Console.WriteLine($"Stage 1 Finished");
    if (!resultOfStage1) return Task.FromResult((string)null);
    return SecondStageAsync();

    async Task<string> SecondStageAsync()
    {
        Console.WriteLine($"Stage 2 Started");
        await Task.Delay(1000); // Simulate an I/O operation
        Console.WriteLine($"Stage 2 Finished");
        return "Hello!";
    }
}

Update: Here is an example of consuming an asynchronous operation that consists of two stages:更新:这是一个使用异步操作的示例,该操作由两个阶段组成:

Task<Task<string>>[] operations = Enumerable.Range(1, 10)
    .Select(_ => TwoStagesNestedAsync_A())
    .ToArray();

/* Do something else before awaiting Stage 1 */

Task<string>[] innerTasks = await Task.WhenAll(operations);
Console.WriteLine($"Stage 1 is now complete");

/* Do something else before awaiting Stage 2 */

string[] results = await Task.WhenAll(innerTasks);
Console.WriteLine($"Stage 2 is now complete");

I assume you want to execute something when first stage is complete.我假设您想在第一阶段完成后执行某些操作。
You can pass an action as parameter to the function.您可以将操作作为参数传递给 function。

public async Task<string> TwoStagesAsync(Func<Task> injectedAction)
{
    await ExecuteStageOne();

    // Execute without "stopping" second stage
    var injectedTask = injectedAction.Invoke();

    if (somethingFailed) return null;
    /* Stage separator */

    await ExecuteStageTwo();

    await injectedTask; // Make sure it completes without errors
    return "Hello!";
}

After update更新后
Requirements tell us that consumer of the TwoStages method do know that operation has two stages and this consumer want execute some action between every stage.需求告诉我们TwoStages方法的使用者确实知道操作有两个阶段,并且该使用者希望在每个阶段之间执行一些操作。
So we need to expose tasks of every state to the consumer.所以我们需要将每个 state 的任务暴露给消费者。
If you wrap TwoStages method within a class, you can expose more details for its consumers.如果将TwoStages方法包装在 class 中,则可以为其使用者公开更多详细信息。
We write code in object-oriented programming language anyway, isn't it;)无论如何,我们都是用面向对象的编程语言编写代码的,不是吗;)

public class TwoStageOperation
{
    public TwoStageOperation() { }

    public async Task ExecuteFirstStage()
    {
        Console.WriteLine($"Stage 1 Started");
        await Task.Delay(1000);
        Console.WriteLine($"Stage 1 Finished");
    }

    public async Task<string> ExecuteLastStage()
    {
        Console.WriteLine($"Stage 2 Started");
        await Task.Delay(1000);
        Console.WriteLine($"Stage 2 Finished");

        return "Hello";
    }
}

Usage用法

var operations = Enumerable.Range(1, 10)
    .Select(_ => new TwoStageOperation())
    .ToArray();

/* Do something else before awaiting Stage 1 */


await Task.WhenAll(operations.Select(op => op.ExecuteFirstStage());
Console.WriteLine($"Stage 1 is now complete");

/* Do something else before awaiting Stage 2 */

string[] results = await Task.WhenAll(operations.Select(op => op.ExecuteLastStage());
Console.WriteLine($"Stage 2 is now complete");

In case operations has different implementations, you can introduce an interface and have different implementations如果操作有不同的实现,你可以引入一个接口并有不同的实现

public interface ITwoStageOperation
{
    Task ExecuteFirstStage();
    Task<string> ExecuteLastStage();
}

var operations = new ITwoStageOperation[]
{
    new LandTwoStageOperation(),
    new OceanTwoStageOperation(),
    new AirTwoStageOperation(),
};

Alternative approach替代方法
Which I think you will prefer more, because you were very close to it:), would be to return a function as result of first stage我认为您会更喜欢它,因为您非常接近它:),将返回 function 作为第一阶段的结果

public async Task<Func<Task<string>>> TwoStagesAsync()
{
    await ExecuteStageOne();

    Func<Task<string>> lastStage = async () =>
    {
         await Task.Delay(1000);
         return "Hello";
    };

    return lastStage;
}

Usage用法

var firstStages = Enumerable.Range(1, 10)
    .Select(_ => TwoStagesAsync())
    .ToArray();

/* Do something else before awaiting Stage 1 */

var lastStages = await Task.WhenAll(firstStages);
Console.WriteLine($"Stage 1 is now complete");

/* Do something else before awaiting Stage 2 */

string[] results = await Task.WhenAll(lastStages.Select(s => s.Invoke());
Console.WriteLine($"Stage 2 is now complete");

Try this.尝试这个。

public static async Task<bool> FirstStageAsync() // Problematic
    {
        Console.WriteLine($"Stage 1 Started");
        await Task.Delay(1000); // Simulate an I/O operation
        bool resultOfStage1 = true;
        Console.WriteLine($"Stage 1 Finished");
        return await Task.FromResult(resultOfStage1);
    }

    public static async Task<string> SecondStageAsync()
    {
        Console.WriteLine($"Stage 2 Started");
        await Task.Delay(1000); // Simulate an I/O operation
        Console.WriteLine($"Stage 2 Finished");
        return "Hello!";
    }

Then you can call it by:然后你可以通过以下方式调用它:

var task = FirstStageAsync().ContinueWith(async d =>
        {
            if (d.IsCompleted)
            {
                var resultOfStage1 = await d;
                if (resultOfStage1)
                {
                    var task2 = SecondStageAsync();
                    task2.Wait();
                }
            }
        });

        task.Wait();

Is that you want to achieve?这是你想要达到的吗?

Be careful with deadlock.小心死锁。 Good Luck!祝你好运!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM