为什么我们需要异步函数

问题描述 投票:0回答:2

我想知道为什么我们需要 C# 中的异步函数。我做了研究,如果有一个 IO 密集型任务想要等待而不阻塞,看起来会使用异步函数。

public class HelloWorld
{
    public static async Task DoIO()
    {
        await Task.Delay(1000, new CancellationTokenSource().Token);
        Console.WriteLine("DONE");
    }

    public static void Main(string[] args)
    {
        DoIO();
        Console.WriteLine("Start");
        while (true);
        // Output: Start \n DONE
    }
}

另一种情况是当一个CPU密集型任务要启动,但我们不想等待它时。它应该在后台运行。它看起来与之前的代码块一模一样,但将

DoIO()
替换为
DoCPU()
,这是一个需要大量计算的函数。

public static async Task DoCPU()
{
    await Task.Run(() => {
        // Heavy computation
    });
}

在这两种情况下,关键字都是“无需等待”。我们希望调用异步方法而不阻塞调用者方法。

现在这就是我的问题出现的地方:为什么我会收到警告“因为不等待此调用,所以在调用完成之前继续执行当前方法。考虑将“await”运算符应用于调用?” 异步函数的全部目的不就是 NOT wait 吗?否则为什么不直接使用默认阻塞的“常规”同步方法呢?

c# asynchronous async-await
2个回答
2
投票

异步函数的全部目的不就是不等待吗?

等待等待是不同的事情。在 .Net 的上下文中,

Wait
是阻塞线程的同步操作。而
await
是异步的,让线程做其他事情。 async/await 的魔力让您能够以与同步代码几乎相同的方式编写异步代码。

考虑两个等效的程序:

public static async Task DoIO()
{
    await Task.Delay(1000);
    Console.WriteLine("End");
}
public static async Task Main(string[] args)
{
    Console.WriteLine("Start");
    await DoIO();
}

public static void DoIO()
{
    Thread.Sleep(1000);
    Console.WriteLine("End");
}
public static void Main(string[] args)
{
    Console.WriteLine("Start");
    DoIO();
}

对于控制台程序来说,这些是直接等效的。使用同步变体是完全可以的。

如果我们从异步变体中删除 Main 方法中的

await
,则程序不再等效。程序将打印“start”,创建一个 1 秒后计时的计时器,然后在主线程到达 Main 方法末尾时退出。从不打印“结束”。这就是您收到警告的原因,不等待任务是一个很容易犯的错误,而且通常是一个错误。

如果这是一个 UI 程序,那就会有区别。如果您在 UI 线程上使用

Thread.Sleep
或任何其他阻塞操作,您的 UI 将冻结,并且很快窗口将显示“应用程序无响应”消息。 Async/await 可以轻松地执行缓慢的操作,同时仍然保持 UI 响应,并且仍然确保只有 UI 线程更新 UI。

异步编程的另一个常见用途是服务器,您可能有数千个并发请求都在等待数据库。对每个请求使用一个线程会浪费内存。如果您使用 async/await,则在实际使用它来计算某些内容时只需要一个线程。

您还可以使用 async/await 并发执行多个 IO 操作,通过启动多个操作,并且仅在所有操作启动后才执行等待。但执行此操作时需要小心一点,因为可能会导致各种争用问题,从而导致降低性能。


1
投票

忽略评论,我会回答这个:

现在这就是我的问题所在:为什么我会收到警告“因为未等待此调用,因此在调用完成之前继续执行当前方法。考虑将“await”运算符应用于调用结果吗? ”异步函数的全部目的不就是不等待吗?否则为什么不直接使用默认阻塞的“常规”同步方法呢?

你似乎有很多误解。

首先,您不是等待方法调用,而是等待它返回的任务。

换句话说:您不必必须这样做:

var result = await DoSomethingAsync();

你完全可以做到这一点:

var task = DoSomethingAsync();

// do something else

var result = await task;

您的代码不会执行任何这些操作。因此,您会收到有关该事实的警告。

我希望第二个片段已经向您展示了为什么

await
ing a
Task
与阻塞完全不同。您无法defer解开同步阻塞方法的上下文。换句话说:您无法像上面所示拆分同步调用。

在这方面,它是

Task<T>
还是
Task
也并不重要。唯一的区别是后者缺少结果值。

还要考虑到,当程序退出时,永远不会等待的“fire&forget”异步任务可能仍在运行/等待 IO,并带来随之而来的所有副作用。

有时,这是故意的,并且已知没有严重的副作用。在这种情况下,您可以随意抑制警告。

© www.soinside.com 2019 - 2024. All rights reserved.