LSThreadPoolLib 1.8.0

LSThreadPoolLib 1.8.0

测试已测试
Lang语言 Obj-CObjective C
许可证 Apache-2.0
发布上次发布2018 年 5 月

Gianluca Bertani 维护。



适用于 iOS 的线程池库

这是 Lightstreamer iOS 客户端库自 1.2 版本以来所使用的线程池和 URL 分派库。

这个库能做什么

这个代码最初在 2012 年编写,是为了解决 iOS SDK 和运行时的一个特定问题。

  • 在 iOS 上,NSURLConnection 对同一端点的并发连接数有 4 的限制。超过这个限制,连接会直接超时,甚至不会尝试。

与此同时,NSURLConnection API 已经被更可配置的 NSURLSession 取代,但是问题仍然存在:iOS 上默认的 NSURLSession 每个主机的最大连接数是 4,超过该请求仍将超时。理论上,您可以增加 HTTPMaximumConnectionsPerHost 参数,但官方文档中提到

另外,根据您的互联网连接,会话可能会使用低于您指定的限制的值。

该库通过控制每个端点提交的 URL 请求的数量来解决此问题,确保超出请求被入队而不是发送到系统中。该库还提供了方法来提前知道请求是否会成功或放入等待队列。最重要的是,该库强制执行 URL 请求中设置的超时。

描述问题的原始文章可在 Lightstreamer 的博客上找到

该库中包含的内容

  • LSURLDispatcher:一个用于控制并发连接数的单例类。

  • LSThreadPool:一个具有线程回收和收集功能的固定线程池实现。

  • LSTimerThread:一个可用于在不使用主线程的情况下运行定时调用的辅助类。

  • LSLog:先前类内部使用的简单日志设施。

LSURLDispatcher

LSURLDispatcher是一个单例类,能够自动初始化。您可以通过以下三种可能的方式之一来启动一个对终端的URL请求:

  • 同步请求:在这种情况下,调度程序将下载请求URL并将其作为NSData交付。如果终端已达到其连接限制,调用者将等待直到连接空闲。

  • 短连接请求:调度程序将异步连接并将事件发送给您的代理,随着连接的进行。如果终端已达到其连接限制,调度程序将在后台等待直到连接空闲。对于只持续几秒钟的短期操作,请使用短连接请求。

  • 长连接请求:只有在终端低于一个特定限制(低于连接限制)时,调度程序才会异步连接;否则它将根据指定的策略进行反应(默认情况下会抛出异常,但其他策略也适用)。对于预期将持续几分钟或更长时间的长期操作(数据流、音频/视频流、VoIP等),请使用长连接请求。

短期和长期请求之间的区别非常重要:一个应用如果对同一终端打开了4个长期请求(如音频、视频和数据流),除非其中有一个请求被终止,否则将无法再次连接到该终端,即使是像下载图标这样的简单请求。通过将短期和长期请求分开并设置不同的限制,库确保短期请求总是有可用的备用连接。Lightstreamer客户端通过将流连接作为长期请求运行,并将控制连接作为短期请求运行来利用这种区别。

要启动一个短期请求,只需这样做:

NSURL *url= [NSURL URLWithString:@"http://some/url"];
NSURLRequest *req= [NSURLRequest requestWithURL:url];

LSURLDispatchOperation *op= [[LSURLDispatcher sharedDispatcher] dispatchShortRequest:req delegate:self];

如果需要,可以在稍后取消请求操作。

[op cancel];

对于长期请求,您还可以提前检查它是否成功(即限制是否已达到)。

if (![[LSURLDispatcher sharedDispatcher] isLongRequestAllowed:req]) {
NSLog(@"Connection limit reached");

} else {
LSURLDispatchOperation *longOp= [[LSURLDispatcher sharedDispatcher] dispatchLongRequest:req delegate:self];
// ...
}

从1.7.0版开始,请求操作在NSURLSession线程上执行。现在,库仅使用自己的线程池来排队超出部分的请求,并解耦代理事件的交付。

从1.8.0版开始,库使用GCD队列来排队超出部分的请求,并解耦代理事件的交付。线程池仍然是库的一部分,但不再由LSURLDispatcher使用。

LSThreadPool

LSThreadPool的使用非常简单。通过定义大小和名称(名称将用于日志记录)来创建它。

// Create the thread pool
LSThreadPool *threadPool= [[LSThreadPool alloc] initWithName:@"Test" size:4];

然后,使用其 scheduleInvocationForTarget:selector:scheduleInvocationForTarget:selector:withObject: 方法调度调用。例如:

[threadPool scheduleInvocationForTarget:self selector:@selector(addOne)];

如果您希望更方便一些,可以使用块。例如:

[threadPool scheduleInvocationForBlock:^() {
    // Do something
}];

最后,完成操作后,在释放线程池之前将其销毁

[threadPool dispose];
threadPool= nil;

如果10秒内有另一个调度调用到达,则回收线程。15秒后,收集器删除空闲线程。

LSTimerThread

LSTimerThread 可以在不使用主线程的情况下对任何对象的任何方法进行延迟调用。使用共享线程来调度调用,所以请确保您的被调用方法执行时间不要太长。

要使用定时器,只需像使用 NSObjectperformSelector:withArgument:afterDelay 一样调度调用即可。例如:

[[LSTimerThread sharedTimer] performSelector:@selector(timeout) onTarget:self afterDelay:timeout];

如果您希望更方便一些,可以使用块。例如:

[[LSTimerThread sharedTimer] performBlock:^() {
    // Do something
} afterDelay:timeout];

LSLog

LSLog 为可分离的来源提供简单的日志记录。不支持日志等级,但通过 LSLogDelegate 协议提供日志代理。

支持的来源包括:

  • LOG_SRC_THREAD_POOL 用于 LSThreadPool
  • LOG_SRC_URL_DISPATCHER 用于 LSURLDispatcher
  • LOG_SRC_TIMER 用于 LSTimerThread

所有日志均视为 DEBUG 级,因此只有需要调试时才启用来源

[LSLog enableSource:LOG_SRC_THREAD_POOL];

要启用代理,只需将您的 LSLogDelegate 实现设置为 LSLog 类。

[LSLog setDelegate:myLogger];

测试用例

包含了一些简单的测试用例。它们展示了对于线程池大小的严格限制,定时调用以及每个端点的连接限制。

许可证

此软件自 1.2 版本以来是 Lightstreamer 的 iOS 客户端库的一部分。它作为开源软件在 Apache License 2.0 许可下发布。有关更多信息,请参阅 LICENSE。