在 C# 中创建和调用异步方法的 lambda 的正确方法

Right way to create and call lambda of async methods in C#

我花了一些时间弄清楚如何在 C# 中“存储”和调用异步 lambda,它基本上是由 运行ning 尝试的。我发现了一些有效而其他方法无效的方法(如下),我阅读了相关内容,但仍然不清楚为什么有些有效而其他无效,有人可以解释为什么并可能添加更好的方法吗?

public static class Program
{
    static async Task Main(string[] args)
    {
        // works fine, everything is printed
        Console.WriteLine("Before 1 delay");
        await Task.Delay(1000);
        Console.WriteLine("After 1 delay");

        // works fine, everything is printed
        var foo = new Func<Task>(async () =>
        {
            Console.WriteLine("Before 2 delay");
            await Task.Delay(1000);
            Console.WriteLine("After 2 delay");
        });
        await foo.Invoke();

        // works fine, everything is printed
        var foo2 = new Action(() =>
        {
            Console.WriteLine("Before 3 delay");
            Task.Delay(1000).Wait();
            Console.WriteLine("After 3 delay");
        });
        foo2.Invoke();

        // Does not work, just before is printed
        var foo3 = new Action(async () =>
        {
            Console.WriteLine("Before 4 delay");
            await Task.Delay(1000);
            Console.WriteLine("After 4 delay");
        });
        foo3.Invoke();
    }
}

请注意,该问题与下面的问题不同,即使答案相同,上下文也不同,因为这个问题基本上就是为什么 Action 委托 运行 async 即使与await 在那里,除此之外还有“为什么 .Await() 有效,而 await 不在动作委托中”的“奖励”

异步任务与异步无效 “await” 不等待调用完成 为什么控制台 window 在显示我的输出后立即关闭? 如何阻止 C# 控制台应用程序自动关闭?

// Does not work, just before is printed
// Action-Delegate => "void" does not return the Task, cannot be awaited.
var foo3 = new Action(async () =>
{
    Console.WriteLine("Before 4 delay");
    await Task.Delay(1000); // Meanwhile, your Program ends!
    Console.WriteLine("After 4 delay");
});
foo3.Invoke(); // <- Starts the action "fire&forget"
//Your Program ends BEFORE second WriteLine can be executed.

^^ 查看代码中的注释。如果你在那之后放置任何东西 foo3.Invoke(); 阻止你的程序至少再退出一秒钟,你会看到“4 次延迟后”输出。

这基本上是一个 async void Method() - 这是一个坏主意,除了异步事件处理程序。

请注意,这还有其他副作用。例如,在 Action.

中出现异常的情况下
var foo2 = new Action(() =>  // _NOT_ async!
{
    Console.WriteLine("Before 3 delay");
    Task.Delay(1000).Wait();  // Blocking!
    Console.WriteLine("After 3 delay");
});
foo2.Invoke(); // Will block. => "works"

你最后一种方法的问题是你用 Action 包装了一个异步 lambda,它描述了一个 void 返回函数。

await Task.Delay(1000); 执行时,它告诉运行时在延迟异步完成后安排继续(即该行之后的指令)。

此时委托的调用者无法等待内部异步函数及其延迟,所以这里我们处于async void 调用,操作最终会完成,但不会返回 Task-like 实例。由于调用者无法等待函数终止,它会继续执行直到 Main 方法退出。

一般来说,async void 应该只用于 asynchronous event handlers,因为如您所见,调用者无法正确等待函数完成。

在 C# 中创建和调用异步方法的 lambda 的正确方法

// return a Task!
var wrapper = new Func<Task>(async () =>
{
    await SomethingAsync();
});
await wrapper.Invoke();