异步消息传递是响应式系统的一个关键特性。但到底是什么异步性,为什么它对响应式应用程序如此重要?我们的人生注定在许多异步任务中。你可能没有意识到,但你的日常活动如果它们本质上不是异步的,那就太烦人了。要理解什么是异步,首先需要理解非异步执行或同步执行。
同步执行意味着您必须等待一个任务完成,然后才能继续执行下一个任务。同步执行的一个现实例子发生在快餐店:你走向柜台的工作人员,在店员等待的时候决定要点什么,点你的食物,一直等到饭菜准备好。店员等着你把钱交出来,然后给你食物。只有这样,你才能继续下一个任务,去你的桌子吃饭。这个序列如图1.14所示。
图1.14同步点餐,每一步都必须完成后才能进入下一步
这种类型的序列感觉像是在浪费时间(或者,更好地说,是在浪费资源),所以想象一下当你为应用程序做同样的事情时,你的应用程序会有什么感觉。下一节将对此进行演示。
一、这都是关于资源的使用
想象一下你的生活会是什么样子,如果在你做其它事情之前,你必须等待每一个操作完成,此时,那会有多少资源处于等待。同样的问题也与计算机科学相关:
writeResult = LongDiskWrite();
response = LongWebRequest();
entities = LongDatabaseQuery();
在这个同步代码片段中,LongDatabaseQuery将不会开始执行,直到LongWebRequest和LongDiskWrite完成。在执行每个方法的过程中,调用线程被阻塞,它所拥有的资源实际上被浪费了,不能用于服务其他请求或处理其他事件。如果这种情况发生在UI线程上,那么应用程序将看起来冻结,直到执行完成。如果这种情况发生在服务器应用程序上,那么在某个时刻,您可能会用完可用线程,并且请求将开始被拒绝。在这两种情况下,应用程序都停止响应。
运行前面的代码片段所需的总时间如下:
total_time = LongDiskWritetime + LongWebRequesttime + LongDatabaseQuerytime
总完成时间是其组成部分的完成时间之和。如果您可以在不等待上一个操作完成的情况下启动一个操作,则可以更好地使用您的资源。这就是异步执行的作用。
异步执行意味着一个操作已经启动,但它的执行是在后台进行的,并且调用者没有被阻止。相反,当操作完成时,会通知调用方。在这段时间里,调用者可以继续做有用的工作。
在点餐示例中,异步方法类似于坐在桌子旁由服务员服务。首先,你坐在桌子旁,服务员来递给你菜单,然后离开。当你决定点什么时,服务员仍然可以为其他顾客服务。当你决定了要什么餐后,服务员会回来帮你点餐。在准备食物的同时,你可以自由聊天、使用手机或欣赏风景。你没有被阻拦(服务员也没有)。当食物准备好后,服务员把它端到你的桌子上,然后回去为其他顾客服务,直到你要求结账并付款。
该模型是异步的:任务并发执行,执行的时间与请求的时间不同。这样,资源(例如
服务员)可以自由处理更多的要求。
异步执行发生在哪里?
在计算机程序中,我们可以区分两种类型的异步操作:基于cpu的和基于I/ o的。
在基于cpu的操作中,异步代码在另一个线程上运行当另一个线程的执行结束时返回结果。
在基于I/O的操作中,操作是在I/O设备(如硬盘)上进行的驱动器或网络。在网络上,向另一台机器发出请求(通过使用TCP)或UDP或其他网络协议),以及机器上的操作系统何时收到信号由网络硬件发出的中断结果返回,则操作将完成。
在这两种情况下,调用线程都可以自由地执行其他任务并处理其他任务请求和事件。
异步运行代码的方法不止一种,这取决于所使用的语言。附录A展示了在c#中实现这一点的方法,并深入研究了每个比特和字节。现在,让我们看一个使用。net future实现的异步工作的例子——Task类:
上述代码片段的异步版本如下所示:
taskA = LongDiskWriteAsync();
taskB = LongWebRequestAsync();
taskC = LongDatabaseQueryAsync();
Task.WaitAll(taskA, taskB, taskC);
在这个版本中,每个方法都返回Task<T>。这个类表示一个正在后台执行的操作。当调用每个方法时,调用线程不会被阻塞,并且该方法会立即返回。然后在上一个方法仍在执行时调用下一个方法。当所有方法都被调用时,您可以使用Task来等待它们的完成。WaitAll方法,该方法获取任务和块的集合,直到所有任务和块都完成为止。另一种写法如下:
taskA = LongDiskWriteAsync();
taskB = LongWebRequestAsync();
taskC = LongDatabaseQueryAsync();
taskA.Wait();
taskB.Wait();
taskC.Wait();
这样,你会得到同样的结果;您等待每个任务完成(当它们仍在后台运行时)。如果在调用Wait方法时任务已经完成,它将立即返回。运行代码片段的异步版本所需的总时间如下:
total_time = MAX(LongDiskWritetime, LongWebRequesttime, LongDatabaseQuerytime)
因为所有方法都是并发运行的(甚至可能是并行运行的),所以运行代码所需的时间将是最长操作的时间。
二、Rx与异步编程
异步执行并不局限于仅通过使用Task<t>来处理。在附录A中,您将了解中使用的其他模式.NET Framework提供异步执行。
回顾IObservable,即时变变量的Rx表示,您可以使用它来表示任何异步模式,因此当异步执行完成(成功或出现错误)时,执行链将运行,并评估依赖关系。Rx提供了将各种类型的异步执行(如Task<T>)转换为IObservable<T>的方法。
例如,在Shoppy应用程序中,不仅当您的位置发生变化时,而且当您的连接状态变为在线时,您都希望获得新的折扣——例如,如果您的手机短时间失去信号,然后重新连接。对Shoppy web服务的调用是以异步方式完成的,当它完成时,您希望更新视图以显示新项目:
IObservable<Connectivity> myConnectivity=...
IObservable<IEnumerable<Discount>> newDiscounts =
from connectivity in myConnectivity
where connectivity == Connectivity.Online
from discounts in GetDiscounts() //GetDiscounts返回一个任务,该任务已隐式转换为可观察项。
select discounts;
newDiscounts.Subscribe(discounts => RefreshView(discounts));//RefreshView显示折扣。
private Task<IEnumerable<Discount>> GetDiscounts()
{
//Sends request to the server and receives the collection of discounts
}
在本例中,您将对myConnectivity可观察到的连接更改做出反应。每次连接发生变化时,您都会检查是否是因为您处于联机状态,如果是,则调用异步Get Discounts方法。方法执行完成后,您可以选择返回的结果。这个结果将被推送给从您的代码中创建的新折扣可观察的观察者。
——未完待续
译者:重庆教主(QQ23611316) 2024.05.14
网站:WPF中文网 wpfsoft.com