我花了一些时间弄清楚如何在 C# 中“存储”和调用异步 lambda,它基本上是通过运行来尝试的。我发现一些方法有效,而其他方法则无效(如下),我读到了相关内容,但仍然不清楚为什么有些方法有效,而另一些方法无效,有人可以解释原因,也许还可以添加更好的方法吗?
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 委托即使在等待存在的情况下也会运行异步,除此之外是“为什么 .Await() 有效,而 wait 不在操作委托中”的“奖励”
异步任务与异步无效 “await”不等待调用完成 为什么控制台窗口在显示我的输出后立即关闭? 如何阻止 C# 控制台应用程序自动关闭?
最后一种方法的问题在于,您用
Action
包装了异步 lambda,它描述了 void
返回函数。
当
await Task.Delay(1000);
执行时,它告诉运行时在延迟异步完成后安排继续(即该行之后的指令)。
此时委托的调用者无法等待内部异步函数及其延迟,因此这里我们处于
async void
调用的上下文中,其中操作最终将完成,但没有Task-like
返回实例。由于调用者无法等待函数终止,因此它会继续执行,直到 Main
方法退出。
一般来说,
async void
应该仅用于异步事件处理程序,因为正如您所见,不允许调用者正确等待函数完成。
在 C# 中创建和调用异步方法的 lambda 的正确方法
// return a Task!
var wrapper = new Func<Task>(async () =>
{
await SomethingAsync();
});
await wrapper.Invoke();
// 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();
之后放置任何阻止程序退出至少一秒的内容,您将看到“After 4 Delay”输出。
这基本上是一个
async void Method()
- 这是一个坏主意,除了异步事件处理程序。
请注意,这还有其他副作用。例如,如果操作中出现异常。
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"
虽然这“有效”,但这是不好的做法。应考虑(并等待)
Func<Task>
。