面试题:async & await(C#)

2025-06-10

面试题:async & await(C#)

async问: /关键词的用途是什么await

这些关键字允许以同步方式编写异步非阻塞代码。

Task此功能由/Task<T>类或ValueTask/结构体实现ValueTask<T>。这些类型表示围绕可能异步执行的操作的抽象。

我们使用await关键字将任务具体化为结果值。包含该关键字的方法await必须使用async关键字标记。


问:异步编程和多线程编程有什么区别?

异步任务并不一定代表在单独的线程上执行。你可以将异步操作视为两个事件(开始和完成)的集合。

异步操作的一个很好的例子是从硬盘读取文件。为了读取文件的单个字节,操作系统会向驱动程序软件发出请求,驱动程序软件会指示硬盘通过移动机械磁头来定位到特定位置。移动磁头的过程是异步的,它不是在 CPU 上运行的操作,而是一个需要等待完成的物理任务。这是一种“纯”异步操作,可以用async/await模式表示。

也就是说,异步任务也可能表示在单独的线程上进行一些 CPU 密集型计算,但这是一个实现细节。当你想将执行委托给其他线程以避免阻塞调用线程,同时又将其伪装成异步操作时,这很有用。你可以调用 来实现Task.Run()

总的来说,每个多线程执行都可以表示为异步操作,但并非每个异步操作都必须采用额外的线程。


问:它是如何工作的?

我们以下面的方法为例:

public async Task DoAsync()
{
    Console.WriteLine("Before await");

    await Task.Delay(TimeSpan.FromSeconds(1));

    Console.WriteLine("Between awaits");

    await Task.Delay(TimeSpan.FromSeconds(1));

    Console.WriteLine("After await");
}
Enter fullscreen mode Exit fullscreen mode

从功能上讲,此代码的工作原理是将“Before await”打印到控制台,等待 1 秒,打印“Between awaits”,再次等待 1 秒,然后打印“After await”。

直到第一个方法之前的所有操作都await像普通方法一样同步执行。这意味着“Before await”将在调用此方法的同一上下文中打印。

接下来发生的事情是,我们使用静态辅助方法创建并运行一个新任务Task.Delay()。这是一个简单的任务,它不执行任何操作,并在指定的延迟后自动变为已完成状态。

当到达该await关键字时,运行时会将控制权返回给调用方法,该方法可能会或可能不会等待它。如果它等待,则再次发生相同的操作,将控制权依次返回给该方法的调用者,直到到达调用堆栈中不等待的方法(通常是消息循环线程上的事件处理程序)或同步实现任务的方法(例如控制台应用程序中的入口点)。

一旦任务完成(1 秒后),执行将返回到我们的方法,该方法将继续打印“Between awaits”。默认情况下,执行将在与开始执行此方法相同的上下文中继续进行。

然后,故事再次重复,运行并等待新任务,最终打印“After await”。在最后一条消息之后,执行将返回给调用者方法(如果它处于 awaited 状态DoAsync),以便它可以继续执行。


问:如果我们执行异步方法但不等待它会发生什么?

没什么特别的,任务所代表的操作将正常延续其生命周期,但结果不会被观察到。任务对象本身最终将被垃圾收集器回收。

请注意,我们还可以使用.ContinueWith()回调方式来处理结果。


问:如果异步方法中抛出异常会发生什么?

如果等待该方法,则异常将立即传播到调用方法,然后传播到该方法的调用者,依此类推,只要等待整个链。

否则,异常将被视为未观察到,这可能导致(在某些版本的框架中)应用程序在终结器处理任务后立即崩溃。


问:是否可以创建异步执行的 lambda?

是的。

var result = await new Func<Task>(async () => await Task.Delay(100));
Enter fullscreen mode Exit fullscreen mode

问:当一个方法Task没有等待就返回时会发生什么?

例子:

public Task WaitAsync() => SomeOtherMethodAsync();
Enter fullscreen mode Exit fullscreen mode

在这种情况下,如果在的异步部分中引发异常SomeOtherMethodAsync(),则该WaitAsync()方法将不会在堆栈跟踪中列出。


问:Task类型实现IDisposable,我们应该什么时候处理任务?

Dispose()方法不应该手动调用,当您等待它或当它被 GC 回收时,任务将被自动处置。


问: 的目的是什么ConfigureAwait()

默认情况下,等待的任务完成后,执行将在最初捕获的上下文中继续,即调用该方法的同一线程。您可以通过指定 来覆盖该行为ConfigureAwait(false),表示执行可以在其他上下文中继续。

通常建议ConfigureAwait(false)尽可能使用它,因为它可以提供轻微的性能提升并有助于防止死锁。


问:为什么默认行为是在捕获的上下文上继续?

出于兼容性原因,在使用 Windows UI 和经典 ASP.NET 应用程序时存在一些限制。在前者中,您只能从主线程与控件交互;在后者中,您只能在处理请求的同一线程上提供响应,因此在捕获的上下文中继续执行至关重要。


Task问:和有什么区别ValueTask

主要区别在于Task是类,而ValueTask是结构体。后者被添加到 BCL 中是为了减轻异步方法(这些方法通常同步返回结果,例如缓存结果)对垃圾收集器造成的不必要压力。您可以将 视为ValueTask的可区分联合Task或同步结果。最近,ValueTask也可以表示由 发出信号的结果ValueTaskSource


问:与同步方法相比,使用异步方法的主要缺点是什么?

异步方法可能更难调试,尤其是因为异步方法抛出的异常难以读取堆栈跟踪。此外,在紧密循环中运行许多异步任务会给垃圾收集器带来压力。

鏂囩珷鏉ユ簮锛�https://dev.to/tyrrrz/interview-question-async-await-c-34in
PREV
面试题:堆 vs 栈 (C#) 前言 堆 vs 栈
NEXT
如何在 JavaScript 中验证电子邮件地址