突然想聊聊 throttle

如果我们将水龙头拧紧知道水是以水滴的形式流出, 不难发现, 每隔一段时间, 就会有一滴水流出来。

如果我们预先设置一个执行周期, 当第一次调用动作(滴水)和第二次调用动作之间的间隔大于执行周期, 则执行这个动作。反正, 不执行。

直接翻译 throttle 这个单词。

1
2
3
4
5
throttle
n. 节流阀; 喉咙,气管; [机] 风门;
vt. 扼杀,压制; 勒死,使窒息; 使节流; (用节汽阀等) 调节;
vi. 节流,减速; 窒息;

不难理解, throttle 其实就是一种控制数据或者流量大小的机制。

MrPeak 曾经写的文章中介绍过 I/O Throttle 在 DISPATCH_QUEUE_PRIORITY_BACKGROUND queue 的使用场景。在没有非常严苛的实时性要求的情况下, 对于重度依赖磁盘的后台任务, 使用 DISPATCH_QUEUE_PRIORITY_BACKGROUND 的 queue 会更加友好。文中也引用了官方文档的一段话

Items dispatched to the queue run at background priority; the queue is scheduled for execution after all high priority queues have been scheduled and the system runs items on a thread whose priority is set for background status. Such a thread has the lowest priority and any disk I/O is throttled to minimize the impact on the system.

意思是说, 这种 Global Queue 会在其他所有的 queue 结束之后才会在后台执行。 这是最低优先级的。并且一些磁盘 I/O 操作会因为最小化对系统的影响而被节流。

文中也介绍了 ASI 和 AFN 中也出现了的 throttle。

我们通过HTTP请求发送数据的时候,实际上数据是以Packet的形式存在于一个Send Buffer中的,应用层平时感知不到这个Buffer的存在。TCP提供可靠的传输,在弱网环境下,一个Packet一次传输失败的概率会升高,即使一次失败,TCP并不会马上认为请求失败了,而是会继续重试一段时间,同时TCP还保证Packet的有序传输,意味着前面的Packet如果不被ack,后面的Packet就会继续等待,如果我们一次往Send Buffer中写入大量的数据,那么在弱网环境下,排在后面的Packet失败的概率会变高,也就意味着我们HTTP请求失败的几率会变大。

所以在弱网环境下, 使用 Network Throttle 机制 延迟发起一些请求或者减少往 Send Buffer 中的数据量。 也会大大的提高请求成功率。

1
2
3
4
5
6
7
8
9
10
11
/**
Throttles request bandwidth by limiting the packet size and adding a delay for each chunk read from the upload stream.
When uploading over a 3G or EDGE connection, requests may fail with "request body stream exhausted". Setting a maximum packet size and delay according to the recommended values (`kAFUploadStream3GSuggestedPacketSize` and `kAFUploadStream3GSuggestedDelay`) lowers the risk of the input stream exceeding its allocated bandwidth. Unfortunately, there is no definite way to distinguish between a 3G, EDGE, or LTE connection over `NSURLConnection`. As such, it is not recommended that you throttle bandwidth based solely on network reachability. Instead, you should consider checking for the "request body stream exhausted" in a failure block, and then retrying the request with throttled bandwidth.
@param numberOfBytes Maximum packet size, in number of bytes. The default packet size for an input stream is 16kb.
@param delay Duration of delay each time a packet is read. By default, no delay is set.
*/
// AFN 中的方法
- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
delay:(NSTimeInterval)delay;

RAC 中的 throttle

1
2
3
[[[self.button rac_signalForControlEvents:UIControlEventTouchUpInside] throttle:2] subscribeNext:^(id x) {
NSLog(@"throttle");
}];

RAC 中的 throttle 接受一个时间间隔 interval 作为参数, 如果 RACSignal 发出的 next 事件之后的 interval 时间内不再发出 next 那么, 他返回的 RACSignal 会将这个事件发出。也就是说,这个方法会将发送比较频繁的 next 事件舍弃,只保留着一段时间之前的 next 事件。

使用场景:

1、处理用户输入框的信号

在处理搜索这样的需求的时候,有时候可能会在用户在输入框输入之后自动跟服务器交互。然后在下面列表中展示处理结果。如果用户打字很快,这个时候可能几秒钟之内连续发去好几个请求,其实没有什么意义。 这个时候使用 throttle 可以将保留用户输入的少数几个结果, 然后返回一个信号, 将最后的文字作为 next 发出。

2、监听按钮点击

界面上又一个按钮, 然后点击这个按钮, 发起网络请求, 跟上面的例子类似。如果用户快速的点击这个按钮也比较蛋疼。按照以前的写法, 我们可能会设置一个 flag 在点击的时候将这个 flag 设置为 no, 然后在 button 的 action 中判断是否发起请求,然后在请求结果回来之后, 把这个 flag 改回来。 想想都比较蛋疼。这时候使用 throttle 将按钮的点击事件节流一下, 就好了。

这个直接搬用 MrPeak iOS编程中throttle那些事中的swift代码。 毕竟 OC 的已经在上面了。

1
2
3
4
5
6
button.rx_tap
.throttle(0.5, MainScheduler.instance)
.subscribeNext { _ in
print("Hello World")
}
.addDisposableTo(disposeBag)

在 OC 的 demo 中(ReactiveCocoa 2.5.0) 我也发现了一个问题。第一次的点击也会在这个间隔之后响应。之后又使用了 ReactiveObjc(3.0) 中也发现了这个问题,而在 RxSwift 的 demo 中并没有这种情况。

总结

其实 throttle 是一种通过减少实际逻辑处理过程的执行来提高事件处理函数运行性能的手段, 它并没有减少事件的触发次数。写到最后, 发现文章有点变成学习心得了😂😂😂😂

CepheusSun wechat
订阅我的公众号,每次更新我都不一定会告诉你!
坚持原创技术分享,您的支持将鼓励我继续创作!
0%