尽管同步、异步和多线程之间的区别对大多数开发人员来说非常清楚,但在实际方面可能会产生一些疑问。
async-await 运算符向列表中添加了另外一种场景,在某些情况下可能会很棘手。
在开始讨论棘手的情况之前,我将简要解释一些基本概念。
过程当一个计算机程序启动时,操作系统(OS)会创建一个进程来执行该程序,通常你可以在你的操作系统的应用程序管理器上看到进程ID,当关闭这个进程时你的应用程序就结束了。
线线程是进程的子集,用于执行应用程序的代码指令,通常一个进程以一个线程开始,但该进程可以创建和完成额外的线程。
关闭进程时,所有相关线程都已完成。
阻塞线程有些任务比其他任务需要更多的时间来执行,例如读取或写入大文件、访问网络资源或连接并针对数据库执行命令。
启动应用程序时,会创建一个新线程并开始执行所有代码指令,例如显示用户界面、呈现内容以及加载按钮和文本框等视觉组件。
想象一下,当点击一个按钮时,应用程序需要读取和处理一个大文件,在这种情况下,当线程忙于读取和处理文件时,用户界面将被阻塞,无法接受任何用户命令,如点击或滚动
同样的情况也可能发生在没有 API 等用户界面的应用程序上,当线程执行任务时,所有其他指令都必须等待。
多线程解决这个问题的方法很简单,我们只需要创建一个额外的线程来执行长流程任务,然后主线程可以执行其他指令,比如让用户点击另一个按钮、滚动屏幕等。
当长进程完成执行时,附加线程关闭,应用程序从他等待的点继续执行。
异步大多数长处理任务由操作系统而不是应用程序管理这一事实表明,创建一个新线程只是为了等待操作系统管理这些任务并不是很聪明。
这就是 async 发挥作用的地方。
异步方法将任务委托给操作系统,不会阻塞主线程,当操作系统完成处理时,它将作为回调返回调用点。
聪明多了,不是吗?
差异下面我们可以通过图形看到同步、异步和多线程之间的执行差异。
同步呼叫。作者的图表
异步调用。作者的图表
等待操作符有时需要一个长任务的结果来执行程序中的下一步,因此程序在操作系统执行任务时无法继续执行其他指令,在这种情况下,我们可以使用等待操作符,它将保持等待任务完成并返回执行下一条指令
重要提示:使用 await 不会阻塞主线程,这意味着在执行任务时应用程序不会无响应
棘手的情况。使用 async-await 使开发人员的体验非常接近同步方法,但实际上并非如此。
不完全了解其工作原理的开发人员将始终使用 async-await 而不考虑实际发生的情况,并且当我们处理多个任务时,我们可以优化并行运行它们的应用程序。
为了更好地理解它,请参阅下面的问题
问题我需要调用三个需要很长时间才能执行的方法,并且只有在所有任务完成后才能返回调用者。
我将演示两种方法并测量每个方法执行所花费的时间。
public class AwaitingTest
{
public async Task AwaitSequencially() { ... }
public async Task AwaitInParallel() { ... }
private async Task wait5sec() => await Task.Delay(5000);
private async Task wait5sec_2() => await Task.Delay(5000);
private async Task wait5sec_3() => await Task.Delay(5000);
}
等待测试类
未优化的方法在这里,我异步调用所有方法并按顺序等待它们。
public async Task AwaitSequencially()
{
var watch = new Stopwatch();
Console.WriteLine("Starting sequencially async calls");
watch.Start();
await wait5sec();
await wait5sec_2();
await wait5sec_3();
watch.Stop();
Console.WriteLine($"finished sequencially async calls in {watch.ElapsedMilliseconds}");
}
AwaitSequentially 方法
优化方法现在,我首先创建所有任务,然后等待所有任务。
public async Task AwaitInParallel()
{
var watch = new Stopwatch();
Console.WriteLine("Starting parallel async calls");
watch.Start();
var a = wait5sec();
var b = wait5sec_2();
var c = wait5sec_3();
Console.WriteLine("Doing other stuff");
await a;
await b;
await c;
watch.Stop();
Console.WriteLine($"finished parallel async calls in {watch.ElapsedMilliseconds}");
}
AwaitInParallel 方法
结果调用这两种方法会有很大不同的结果
截图
执行结果
第一种方法在 15 秒后完成,而第二种方法只用了 5 秒,因为它并行运行所有调用。
尽管 async-await 模型与同步模型非常相似,但它们并不相同,并且差异可能很大。