并发是多件事情同时发生的一个概念。
在过去,在应用中引入并发就需要创建一个或者多个额外的线程。不幸的是,写线程代码是一个挑战。线程是低层次工具必须手动的管理。给定应用可选的线程数会基于当前系统负载和硬件动态的改变。实现一个正确的线程方案变得额外的困难,很难达到。并且,线程同步机制的使用会增加软件的复杂度和风险而且还不能保证提升性能。
iOS和OS X都提供了一个比传统基于线程更加异步的方式给多任务的执行。不用直接创建线程,应用只需要指定任务并且让系统执行他们。开发者获取了更加简单和高效的编程模型。
尽管线程已经使用了多年并且会继续发挥作用,他们不再解决一般问题的多任务了。使用线程时,创建一个可衡量的解决方案在你的肩上。你需要决定创建多少个线程并且随着系统条件的变化调整线程数量。另外一个问题是你的程序承担大多数线程创建和维护的开销。
为了替换直接依赖线程,OS X 和iOS采用了一个异步设计方式来解决并发问题。异步函数引入操作系统多年了并且经常使用来执行花长时间运行的任务,比如从磁盘读取数据。当调用时,一个异步函数在后台做这些任务并且在完成后返回。典型的,这个工作涉及到一个后台线程,在线程上开始这个工作,完成以后发送一个通知给调用方。过去,如果你希望做的事情的异步函数不存在,你必须写你自己的异步函数然后创建你的线程。现在IOS和OS X提供了技术允许你来执行任何异步任务不需要自己管理线程。
其中一个技术执行异步任务的是Grand Central Dispatch(GCD).这个技术把你通常需要写在你应用中的线程管理代码交给了系统层面。所有你需要做的就是定义你希望执行的任务把他们添加到一个合适的dispatch queue上。GCD创建需要的线程来并且将你的任务放在这些线程上执行。因为线程管理是系统的一部分了,GCD提供一个全部的方法来管理任务的执行,提供比传统线程更好的效率。
Operation Queues是Objective-C对象类似于dispatch queues的作用。你定义你的任务然后添加任务到operation queue,它处理任务执行的调度。像GCD一样,operation queues处理线程管理的所有事情,确保你的任务在系统上执行得又快又有效率。
Dispatch Queues
Dispatch Queues是一个基于C的机制来执行用户任务。一个Dispatch Queue执行任务可以是串行的也可以是并发的。但是总是先进先出的。一个串行的Dispatch Queue一次只运行一个任务,直到任务完成后再从队列头上取出下一个任务开始执行。相对而言,一个并发执行的Dispatch Queue启动它能启动的尽可能多的任务,不需要等待。
Dispatch Queues有以下优点:
他们提供直接而简单的编程接口
他们提供自动和完全的线程池管理
他们提供协作组件的速度。
他们更加内存高效(因为线程栈不在应用的内存中,而在内核的内存中?)
他们不会导致内核负载过高
他们使用 dispatch queue异步处理任务,不会让queue死锁
他们在竞争中保持平和。
串行dispatch queue提供了一个比锁和其他同步机制更加高效的选择
你提交给dispatch queue的任务必须封装在一个函数或者一个block 对象中。block对象是在ios4中的类似于函数指针但有有额外优点的c语言特性。与其定义blocks在他们自己的词法作用域,你一般定义blocks在他们能访问的一个函数或者方法内部。blocks可以移出他们原始的范围然后复制到堆上,这就是当你将他们提交到dispatch queue上发生的。所有这些语义使得使得少量代码就可以实现非常动态的任务成为可能。
Dispatch queues是GCD技术的一部分和C运行时的一部分。
Dispatch Sources
Dispatch Sources是基于C的机制处理指定类型的系统异步事件。一个dispatch source封装包括特定类型的系统事件的信息然后提交给一个指定的block对象或者函数给dispatch queue当事件发生时。你可以使用dispatch sources来监视以下系统事件
定时器(Timers)
信号处理(Signal handlers)
描述符相关的事件(Descriptor-related events)
进程相关事件(Process-related events)
端口事件(Mach port events)
你触发的用户事件(Custom events that you trigger)
Dispache sources是GCD技术的一部分。
Operation Queues
一个Operation Queue是一个Cocoa等价的并发dispatch queue,它是通过NSOperationQueue类来实现。
dispache queue执行任务是先进先出的,operation queue会考虑其他因素来决定任务的执行顺序。这些因素中的主要因素是给定任务依赖于其他任务的完成。你配置依赖关系当定义你的任务时,然后使用他们来创建负责的执行顺序。
你提交给operation queue的任务必须是NSOperation类的实例。一个operation 对象是一个Objective C的对象封装了你需要执行的工作和你执行时需要的数据。因为NSOperation类本质是一个抽象类,你需要定义自己的子类来执行你的任务。然而,Foundation 框架提供了一些具体的子类你能够创建和使用来执行任务。
Operation Object产生key-value observing KVO通知,这是一个有用的方法来监视你任务的完成情况。尽管operation queues 总是执行operations 并发的,需要时你可以使用依赖关系来确保他们串行的执行。
异步设计技术
在你开始重新设计你的代码来支持并发时,你需要问问你自己是否真的需要。并发能够提高你代码的响应能力来确保主线程空闲的响应用户事件。并发能提高你代码的执行效率,更多的核心执行更多的任务同一时间。然后,它也增加了代码复杂度难以编写和调试。
因为它增加了复杂度,并发不是一个你可以随意加给你的程序特性。正确的增加并发需要仔细的思考你程序执行的任务和执行这些任务需要的数据结构。正确的完成了以后,你可以发现你的代码比之前更加慢并且更少的响应用户了。因此,在开始你的设计时设定一些目标然后思考一些你需要采用的方法是值得的。
每一个应用有不同的需求和不同的需要执行的任务。一个文档不可能完整的告诉你如何设计你的应用和它的任务。然而,以下部分尝试提供一些引导来帮助你在设计过程中做出正确的选择。
定义你的应用程序的预期的行为
在思考给你的程序添加并发之前,你必须先定义你期待你的应用的正确的行为。明白你的应用的预期行为可以为你随后验证你设计提供方法。同时给你了一些主意关于你引入并发带来的预期的执行效果的收益。
什么时候使用线程
尽管operation queues和dispatch queues是执行并发任务的优先选择,但是它们不是万能药。依赖于你的应用,可能有一些任务你需要创建自定义的线程。如果你创建用户线程,你必须努力创建尽可能少的线程并且你必须只有在特殊的任务不能通过另外的方法来实现时使用这些线程。
线程仍然是一种实现实时运行代码的很好的方式。Dispatch queues让每个任务尽可能的快但是它们并不遵守实时运行的约束。如果你需要你的代码运行更加精准的行为,线程仍然是一个更好的选择。
使用线程编程,你必须谨慎的使用线程并且只有在绝对必须的时候才使用。
Operations Queues
Cocoa operations是一个面向对象的方式来封装你需要异步执行的任务。Operations设计的即可以联合一个operation queue使用也可以单独使用。因为它们是基于Objective C,operations是使用在iOS和OS X中基于Cocoa的应用程序中最常见的。
关于Operation Objects
一个operation对象是一个NSOperation类的实例,你用来封装你希望你的应用程序执行的任务。NSOperation类本身是一个抽象基类必须进行子类化来做一些有用的工作。尽管是抽象的,这个类提供了一些重要的基础概述来最小化你的子类需要做的工作。另外,基础框架提供了两个具体化的子类你可以使用。
在过去,在应用中引入并发就需要创建一个或者多个额外的线程。不幸的是,写线程代码是一个挑战。线程是低层次工具必须手动的管理。给定应用可选的线程数会基于当前系统负载和硬件动态的改变。实现一个正确的线程方案变得额外的困难,很难达到。并且,线程同步机制的使用会增加软件的复杂度和风险而且还不能保证提升性能。
Dispatch Queues是一个基于C的机制来执行用户任务。一个Dispatch Queue执行任务可以是串行的也可以是并发的。但是总是先进先出的。一个串行的Dispatch Queue一次只运行一个任务,直到任务完成后再从队列头上取出下一个任务开始执行。相对而言,一个并发执行的Dispatch Queue启动它能启动的尽可能多的任务,不需要等待。
他们提供直接而简单的编程接口
他们提供自动和完全的线程池管理
他们提供协作组件的速度。
他们更加内存高效(因为线程栈不在应用的内存中,而在内核的内存中?)
他们不会导致内核负载过高
他们使用 dispatch queue异步处理任务,不会让queue死锁
他们在竞争中保持平和。
串行dispatch queue提供了一个比锁和其他同步机制更加高效的选择
Dispatch Sources是基于C的机制处理指定类型的系统异步事件。一个dispatch source封装包括特定类型的系统事件的信息然后提交给一个指定的block对象或者函数给dispatch queue当事件发生时。你可以使用dispatch sources来监视以下系统事件
定时器(Timers)
信号处理(Signal handlers)
描述符相关的事件(Descriptor-related events)
进程相关事件(Process-related events)
端口事件(Mach port events)
你触发的用户事件(Custom events that you trigger)
一个Operation Queue是一个Cocoa等价的并发dispatch queue,它是通过NSOperationQueue类来实现。
dispache queue执行任务是先进先出的,operation queue会考虑其他因素来决定任务的执行顺序。这些因素中的主要因素是给定任务依赖于其他任务的完成。你配置依赖关系当定义你的任务时,然后使用他们来创建负责的执行顺序。
在你开始重新设计你的代码来支持并发时,你需要问问你自己是否真的需要。并发能够提高你代码的响应能力来确保主线程空闲的响应用户事件。并发能提高你代码的执行效率,更多的核心执行更多的任务同一时间。然后,它也增加了代码复杂度难以编写和调试。
在思考给你的程序添加并发之前,你必须先定义你期待你的应用的正确的行为。明白你的应用的预期行为可以为你随后验证你设计提供方法。同时给你了一些主意关于你引入并发带来的预期的执行效果的收益。
什么时候使用线程
尽管operation queues和dispatch queues是执行并发任务的优先选择,但是它们不是万能药。依赖于你的应用,可能有一些任务你需要创建自定义的线程。如果你创建用户线程,你必须努力创建尽可能少的线程并且你必须只有在特殊的任务不能通过另外的方法来实现时使用这些线程。
线程仍然是一种实现实时运行代码的很好的方式。Dispatch queues让每个任务尽可能的快但是它们并不遵守实时运行的约束。如果你需要你的代码运行更加精准的行为,线程仍然是一个更好的选择。
使用线程编程,你必须谨慎的使用线程并且只有在绝对必须的时候才使用。
Cocoa operations是一个面向对象的方式来封装你需要异步执行的任务。Operations设计的即可以联合一个operation queue使用也可以单独使用。因为它们是基于Objective C,operations是使用在iOS和OS X中基于Cocoa的应用程序中最常见的。
一个operation对象是一个NSOperation类的实例,你用来封装你希望你的应用程序执行的任务。NSOperation类本身是一个抽象基类必须进行子类化来做一些有用的工作。尽管是抽象的,这个类提供了一些重要的基础概述来最小化你的子类需要做的工作。另外,基础框架提供了两个具体化的子类你可以使用。
1. 支持建立与其他operation对象的依赖关系。这些依赖关系阻止一个operation运行直到所有的它依赖的operations完成运行。
2. 支持一个可选的完成block,在operation的主任务完成后执行。
3. 支持通过KVO通知来监视你的operations的执行状态的变化
4. 支持优先级的operations并且因此影响它们的执行顺序
5. 支持退出语法来允许你停止一个正在运行的operation。
尽管你通常执行operations是将它们添加到一个operation queue中,这并不是必须的。可以通过调用它的start方法来手动的执行一个operation对象,但是这样做不能保证这个operation与其他的代码并行运行。在start方法调用后,operation类的isConcurrent方法告诉你一个operation是同步运行还是异步运行的。这个方法默认是返回NO,意味着operation在它的调用线程中是同步运行的。
NSInvocationOperation类是NSOperation地一个具体地子类,运行时,调用你指定的对象和selector。使用这个类来避免为你程序中每个任务都定义大量的用户operation对象。特别是如果你修改一个已经存在的程序和已经存在的对象和方法需要执行必须的任务。当你希望调用的方法依赖环境能够变化时你可以使用它。比如,你可以使用一个invocation operation来执行一个基于用户输入动态选择的selector。
- ( NSOperation *)taskWithData:( id )data
{
NSInvocationOperation *invocationOp = [[ NSInvocationOperation alloc ] initWithTarget : self selector : @selector (myTaskMethod:) object :data];
return invocationOp;
}
- ( void )myTaskMethod:( id )data
{
NSString *inputString = data;
//the actual work you want to do
}
NSBlockOperation类是NSOperation的一个具体的子类,充当一个或者多个block对象的包装器。这个类为已经使用operation queue并且不希望创建dispatch queue的应用程序提供了一个面向对象的包装。你可以使用block operations来获取operation依赖性,KVO通知,和其他的一些特性你不能从dispatch queue获取。
NSBlockOperation *blockOp = [ NSBlockOperation blockOperationWithBlock :^{
NSLog ( @"Beginning operation.\n" );
[ self myTastInBlockOp ];
}];
完成block operation对象的创建后,你可以使用addExecutionBlock:方法来添加更多的block。 如果你需要串行的执行block,你需要将它们提交到需要的dispatch queue。这里的意思就是不要使用NSBlockOperation,而是使用GCD?
如果block operation和invocation opetation对象都不能满足你的需求,你可以直接子类化NSOperation并且添加你需要的任何行为。NSOperation类提供了一个基本的子类化的线路。operation类提供了大量的基础来处理大部分的 依赖于的KVO通知的 工作。然而,当你需要支持已经存在的基础来而确保你的operations的行为正确。大量的额外的工作你需要做的依赖于你是实现一个并发和还是非并发的operation。
每个operation对象必须至少 实现以下的方法:
1.一个用户自定义的初始化方法
2.main方法
你需要一个自定义的初始化方法将你的operation对象放入一个已知的状态并且用一个自定义的main方法来执行你的任务。你可以根据需要实现额外的方法,当然,就像如下:
1.自定义方法你打算从main方法中调用的。
2.accessor方法来设定和获取操作结果
3.NSCoding协议的方法来允许你序列化和反序列化opertion对象。
@interface MyNonConcurrentOperation : NSOperation
@property id (strong) myData;
-( id )initWithData:( id )data;
@end
@implementation MyNonConcurrentOperation
- ( id )initWithData:( id )data {
if ( self = [ super init ])
myData = data;
return self ;
}
-( void )main {
@try {
// Do some work on myData and report the results.
}
@catch (...) {
// Do not rethrow exceptions.
} }
@end
如何实现一个NSoperation子类的更详细的例子,请看NSOperationSample
一个operation开始执行之后,它会继续执行它的任务直到它结束或者你的代码显示的退出operation。退出可能会在任何时间段发生,甚至在一个operation开始执行前。尽管NSOpertion类提供给客户端一个方法来退出一个operation,认识到退出事件必要的时候采用。当一个operation被立即的结束了,可能就没有方法来回收已经被分配的资源了。结果是,operation对象希望检查退出事件并且平静的退出当它们发生在operation的中间时。
1.立即调用在你执行任何实质性的工作前
2.在循环的每次迭代中至少调用一次,或者每次迭代时间很长的化多调用几次。
3.你代码的任何地方可能相对来说比较容易中止operation。
- ( void )main {
@try {
BOOL isDone = NO ;
while (![ self isCancelled] && !isDone) {
// Do some work and set isDone to YES when finished
} }
@catch (...) {
// Do not rethrow exceptions.
}
}
operation对象执行默认是同步的,它们通过调用start方法在线程上执行自己的任务。因为 operation queues为非并发的 operations 提供线程,所以,大部分 operations仍然是以异步的方式运行。然而,如果你打算手动执行 operations并且仍然希望它们异步运行,你必须采取一些合适的措施来确保这样。你可以通过定义你的 operations对象成一个并发的 operations。
本部分的剩余部分展示了一个示例实现MyOperation类,这个类阐述了实现一个并发的operation基础的代码。MyOperation类简单的在一个它自己创建的分离的线程上执行它自己的main方法。main方法执行的实际的工作是不相关的。这个例子的目的是阐述了基本的构建你需要提供来定义一个并发operation。
下面展示了MyOperation的接口和部分实现。isConcurrent,isExecuting,isFinished方法的实现相对来说很直接。isConcurrent方法必须返回YES来指明它是一个并发的operation。isExecuting和isFinished方法简单的返回存储在实例变量中的值。
BOOL executing;
BOOL finished;
}
- ( void )completeOperation;
@end
- ( id )init {
self = [ super init ];
if ( self ) {
executing = NO ;
finished = NO ;
}
return self ;
}
- ( BOOL )isConcurrent {
return YES ;
}
- ( BOOL )isExecuting {
return executing ;
}
- ( BOOL )isFinished {
return finished ;
}
下面展示MyOperation类的start方法。这个方法的实现是最小化的仅仅为了阐明你必须执行的任务。在这样的情况下,这个方法简单的启动一个新线程并且配置它来调用main方法。这个方法同时更新executing成员变量并且为键路径为isExecuting产生KVO通知来反映这个值的改变。这些工作完成后,简单的返回,留下新分离的线程去执行实际的工作。
- ( void )start {
// Always check for cancellation before launching the task.
if ([ self isCancelled])
{
// Must move the operation to the finished state if it is canceled.
[ self willChangeValueForKey: @"isFinished" ];
finished = YES ;
[ self didChangeValueForKey: @"isFinished" ];
return ; }
// If the operation is not canceled, begin executing the task.
[ self willChangeValueForKey: @"isExecuting" ];
[NSThread detachNewThreadSelector: @selector (main) toTarget: self withObject: nil ];
executing = YES ;
[ self didChangeValueForKey: @"isExecuting" ];
}
- ( void )main {
@try {
// Do the main work of the operation here.
[ self completeOperation];
}
@catch (...) {
// Do not rethrow exceptions.
} }
- ( void )completeOperation {
[ self willChangeValueForKey: @"isFinished" ];
[ self willChangeValueForKey: @"isExecuting" ];
executing = NO ;
finished = YES ;
[ self didChangeValueForKey: @"isExecuting" ];
[ self didChangeValueForKey: @"isFinished" ];
}
)
维持KVO遵从性(Compliance)
operation类为下面这些键路径KVO遵从:
isCancelled;
isConcurrent
isExecuting
isFinished
isReady
dependencies
queuePriority
completionBlock
如果你希望实现支持依赖其他operation对象时,你可以重新isReady方法并且强制它返回NO直到你自定义的依赖被满足。(如果你实现自定义的依赖,如果你仍然支持NSOperation类提供的默认的依赖管理系统,确保在你的isReady方法中调用super)当你的operation对象 准备就绪的状态 改变时,为isReady键路径产生KVO通知来报告这些改变。除非你重些addDependency:或者removeDependency:方法,你不用担心为dependencied键路径产生KVO通知。
operation对象的配置发生在你已经创建了它们之后但是在将它们添加到queue之前。本部分 描述的 配置的类别可以应用在所有的operation对象中,无论你使用已经存在的子类还是自定义NSOperation。
依赖你串行执行operation对象的 是一种方式。一个operation依赖其他的operations不能开始执行直到所有它依赖的operations已经完成执行。因此,你可以使用依赖性来创建简单的两个operation对象间一对一的依赖关系或者构建复杂的对象依赖关系。
重要:你必须总是在运行你的operations或者添加它们到一个operation queue之前配置依赖关系。依赖关系添加以后可能不会阻止一个给定的operation对象执行。
把 operation添加到一个queue中,执行顺序的首要决定因素是queue operation的准备就绪和它们相对的优先级。准备就绪取决于一个operation和其他operations的依赖关系,但是优先级是一个operation对象自身的属性。默认的,所有的新的operation对象有一个“normal”优先级,但是你可以添加或者降低这个优先级通过调用对象的setQueuePriority:方法。
一个operation可以在它的主任务结束执行时执行一个完成block。你可以使用一个完成block做任何你认为不是主任务部分的工作。比如,你可能使用这个block通知感兴趣的客户端你的operation已经执行完了。一个并发的operation对象可能使用这个block来产生它最后的KVO通知。
使用NSOperation的setCompletionBlock:方法来设置一个完成block。你传递给这个方法的block必须是没有参数和没有返回值的。
尽管可以添加一个任意大数量的operations到一个operation queue中,这样做通常是不实际的。就像其他的对象,NSOperation类的实例消耗内存并且它们的执行有真实的开销。如果每个你的operation对象处理少量的工作,你创建了数万个这样的operations,你可能发现你花费了太多的时间调度operations而不是做实际的工作。如果你的应用程序已经受到了内存约束,你可能发现数万的operation对象在内存中甚至降低你的执行效率。
最后,你的应用需要执行operations来做关联的工作。在本部分,你可以学习几种方式来执行operations,你如何能在runtime时操作你的operations的执行。
目前为止,执行operations最简单的方式就是使用一个operation queue,是NSOperationQueue的一个实例。你的应用负责创建和保持任何它打算使用的operation queues。一个应用可以拥有任意数量的queues,但是在同一个时间点执行的operations有实际的限制。operation queue和系统限制并发的operation数量为一个值,这个值对于CPU的可用核心数和系统负载有关。因此,创建额外的queues并不意味着你可以执行额外的operations。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
添加operations到queue中,你可以使用addOperation:方法。在OS X v10.6和以后,你可以使用addOperations:waitUntilFinished:添加operations组,或者你可以使用addOperationWithBlock:方法添加block对象到queue中。每个方法都向队列中加了一个operation并且通知queue必须准备处理它们。在大部分情况下,operations在被添加到queue中不久后就会被执行,但是operation queue可能会因为一些原因延迟加入到队列的operations的执行。特别是,执行可能会被推迟如果加入队列的operation依赖于其他的operations并且还没有完成。执行可能会被推迟如果operation queue自己被暂停了或者它已经执行了它的最大数量的并发operations。如下实例展示了添加operations到一个queue中的基本的语法。
[aQueue addOperation:anOp]; // Add a single operation
[aQueue addOperations:anArrayOfOps waitUntilFinished: NO ]; // Add multiple operations [aQueue addOperationWithBlock:^{
/* Do something. */
}];
重要:永远不要修改一个operation对象在它已经被添加到queue中以后。当在queue中等待时,operation可能在任何时候开始执行,所以改变它的依赖关系或者它包含的数据可能造成不利的影响。如果你希望直到 operation的状态,你可以使用NSOperation类的方法来确定 operation是否在运行,等待运行或者已经结束了。
尽管 operation queues是运行 operation对象最方便的方式,也可以运行 operations不通过queue。如果你选择手动运行 operation,然而,你的代码中有一些需要主要的地方。特别是, operation必须准备好运行并且你使用start方法来启动 operation。
- ( BOOL )performOperation:( NSOperation *)anOp
{
BOOL ranIt = NO ;
if ([anOp isReady ] && ![anOp isCancelled ])
{
if (![anOp isConcurrent ])
{
[anOp start ];
}
else
[ NSThread detachNewThreadSelector : @selector (start) toTarget :anOp withObject : nil ];
ranIt = YES ;
}
else if ([anOp isCancelled ])
{
[ self willChangeValueForKey : @"isFinished" ];
[ self willChangeValueForKey : @"isExecuting" ];
_executing = NO ;
_finished = YES ;
[ self didChangeValueForKey : @"isExecuting" ];
[ self didChangeValueForKey : @"isFinished" ];
ranIt = YES ;
}
return ranIt;
}
一旦加入到 operation queues, operation对象实际就是属于queue了并且不能退出。退出queue的唯一方法就是cancel operation。你可以通过调用cancel方法退出一个单独的 operation对象或者你可以调用cancelAllOperations方法从queue种退出所有的 operation对象。
等待Operations的完成
为了达到最好的性能,你应该尽可能的把你的operations设计成异步的,当operation在执行时释放你的应用做更多的工作。如果创建operation的代码同时处理operation的结果,你可以使用NSOperation的waitUtilFinished方法来阻塞代码直到operation完成。通常来说,最好避免调用这个方法如果你能帮助它的话。阻塞当前线程可能是一个方便的解决方案,但是它引入了更多的串行到你的代码中并且限制了并发的数量。
important:你应该永远不要在你应用的主线程中等待一个operation。你只应该在分离的线程中或者另一个operation中做。阻塞你的主线程阻止你的应用响应用户事件和导致你的应用出现不响应的情况。
除了等待一个单一的operation完成,你也可以使用NSOperationQueue的waitUtilAllOperationAreFinished方法来等待队列中所有的
operations的完成。当等待一个完整的队列的完成时,注意你应用程序的其他线程仍然可以添加operations到队列中,因此可以延长等待。
暂停和恢复Queues
如果你希望暂时停止operations的
执行
,你可以使用setSuspended:方法暂停对应的operation queue。暂停一个queue不会造成已经在运行的operations停止在它们的任务的中间。它仅仅阻止新的operations被安排执行。你可能暂停一个queue来响应用户请求暂停所有要进行的工作,因为期望的是用户可能最终希望恢复这个工作。