多線程開發是一件需要特別精心的事情,即使是對有多年開發經驗的工程師來說。
為了能讓初級開發工程師也能使用多線程,同時還要簡化複雜性。各種編程工具提供了各自的辦法。對於iOS來說,建議在儘可能的情況下避免直接操作線程,使用比如NSOperationQueue這樣的機制。
可以把NSOperationQueue看作一個線程池,可往線程池中添加操作(NSOperation)到隊列中。線程池中的線程可看作消費者,從隊列中取走操作,並執行它。
你可以設定線程池中只有一個線程,這樣,各個操作就可以認為是近似的順序執行了。為什麼說是近似呢,後面會做解釋。
編寫最簡單的樣本
先寫個最簡單的樣本。
編寫一個NSOperation的子類,只需實現main方法。這裡非常類似Java的Thread,你可以繼承它,並覆蓋run方法,在該方法裡面寫入需要執行的代碼。這裡的main方法和run方法作用是相似的。
標頭檔:
@interface MyTask : NSOperation {
int operationId;
}
@property int operationId;
@end
這裡的operationId屬性不是必須的,是我想在後面標識區分多個Task的標識位。
m檔案:
@implementation MyTask
@synthesize operationId;
- (void)main{
NSLog(@"task %i run … ",operationId);
[NSThread sleepForTimeInterval:10];
NSLog(@"task %i is finished. ",operationId);
}
@end
這裡類比了一個耗時10秒鐘的操作。
下面需要把Task加入到隊列中:
- (void)viewDidLoad {
[super viewDidLoad];
queue=[[NSOperationQueue alloc] init];
int index=1;
MyTask *task=[[[MyTask alloc] init] autorelease];
task.operationId=index++;
[queue addOperation:task];
我直接找了個Controller的方法寫上了。運行結果是,介面出現了,而task還未執行完,說明是多線程的。10秒鐘後,日誌列印完畢,類似這樣:
2011-07-18 15:59:14.622 MultiThreadTest[24271:6103] task 1 run …
2011-07-18 15:59:24.623 MultiThreadTest[24271:6103] task 1 is finished.
可以向操作隊列(NSOperationQueue)增加多個操作,比如這樣:
- (void)viewDidLoad {
[super viewDidLoad];
queue=[[NSOperationQueue alloc] init];
int index=1;
MyTask *task=[[[MyTask alloc] init] autorelease];
task.operationId=index++;
[queue addOperation:task];
task=[[[MyTask alloc] init] autorelease];
task.operationId=index++;
[queue addOperation:task];
}
那麼列印出的內容是不定的,有可能是這樣:
2011-07-18 15:49:48.087 MultiThreadTest[24139:6203] task 1 run …
2011-07-18 15:49:48.087 MultiThreadTest[24139:1903] task 2 run …
2011-07-18 15:49:58.122 MultiThreadTest[24139:6203] task 1 is finished.
2011-07-18 15:49:58.122 MultiThreadTest[24139:1903] task 2 is finished.
甚至有可能是這樣:
2011-07-18 15:52:24.686 MultiThreadTest[24168:1b03] task 2 run …
2011-07-18 15:52:24.685 MultiThreadTest[24168:6003] task 1 run …
2011-07-18 15:52:34.708 MultiThreadTest[24168:1b03] task 2 is finished.
2011-07-18 15:52:34.708 MultiThreadTest[24168:6003] task 1 is finished.
因為兩個操作提交的時間間隔很近,線程池中的線程,誰先啟動是不定的。
那麼,如果需要嚴格意義的順序執行,怎麼辦呢?
處理操作之間的依賴關係
如果操作直接有依賴關係,比如第二個操作必須等第一個操作結束後再執行,需要這樣寫:
queue=[[NSOperationQueue alloc] init];
int index=1;
MyTask *task=[[[MyTask alloc] init] autorelease];
task.operationId=index++;
[queue addOperation:task];
task=[[[MyTask alloc] init] autorelease];
task.operationId=index++;
if ([[queue operations] count]>0) {
MyTask *theBeforeTask=[[queue operations] lastObject];
[task addDependency:theBeforeTask];
}
[queue addOperation:task];
這樣,即使是多線程情況下,可以看到操作是嚴格按照先後次序執行的。
控制線程池中的線程數
可以通過類似下面的代碼:
[queue setMaxConcurrentOperationCount:2];
來設定線程池中的線程數,也就是並行作業數。預設情況下是-1,也就是沒有限制,同時運行隊列中的全部操作。