2013-01-09 62 views
3

這是一個兩部分問題。希望有人可以回答一個完整的答案。併發操作所需的說明,NSOperationQueue和異步API

NSOperation s是強大的對象。它們可以是兩種不同的類型:非併發或併發。

第一種類型同步運行。您可以通過將其添加到NSOperationQueue中來利用非併發操作。後者爲你創建一個線程。結果包括以並行方式運行該操作。唯一的警告是關於這種操作的生命週期。當它的main方法結束時,它將從隊列中移除。在處理異步API時,這可能會成爲問題。

現在,併發操作呢?從蘋果公司的文檔

如果你想實現一個併發的操作,也就是說,一個相對於調用線程,你必須寫 額外的代碼異步運行 異步開始操作。例如, 可能會產生一個單獨的線程,調用異步系統功能或執行其他任何操作來確保啓動方法啓動 任務並立即返回,並且很可能在完成 任務之前。

這對我來說已經很清楚了。他們異步運行。但是你必須採取適當的行動來確保他們這樣做。

我不清楚的是以下幾點。醫生說:

注:在OS X v10.6中,操作隊列忽略 isConcurrent返回並隨時調用從 單獨的線程的操作啓動方法的價值。

它的真正含義是什麼? 如果我在NSOperationQueue中添加併發操作,會發生什麼情況?

然後,在此帖子Concurrent Operations中,併發操作用於通過NSURLConnection(以其異步形式)下載某些HTTP內容。操作是併發的並且包含在特定隊列中。

UrlDownloaderOperation * operation = [UrlDownloaderOperation urlDownloaderWithUrlString:url]; 
[_queue addOperation:operation]; 

由於NSURLConnection需要一個循環運行,作者分流start方法在主線程(所以我想添加操作到隊列它產生一個不同的一個)。以這種方式,主運行循環可以調用操作中包含的委託。

- (void)start 
{ 
    if (![NSThread isMainThread]) 
    { 
     [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO]; 
     return; 
    } 

    [self willChangeValueForKey:@"isExecuting"]; 
    _isExecuting = YES; 
    [self didChangeValueForKey:@"isExecuting"]; 

    NSURLRequest * request = [NSURLRequest requestWithURL:_url]; 
    _connection = [[NSURLConnection alloc] initWithRequest:request 
                delegate:self]; 
    if (_connection == nil) 
     [self finish]; 
} 

- (BOOL)isConcurrent 
{ 
    return YES; 
} 

// delegate method here... 

我的問題是以下內容。 此線程安全嗎?運行循環偵聽源代碼,但調用的方法在後臺線程中調用。我錯了嗎?

編輯

我已經完成了我自己的一些測試基於由Dave Dribin提供的代碼(見1)。我注意到,正如你所寫的,NSURLConnection的回調在主線程中被調用。

好吧,但現在我仍然非常困惑。我會試着解釋我的疑惑。

爲什麼在一個併發操作中包含一個異步模式,其中在主線程中調用其回調函數?將start方法調度到主線程,它允許在主線程中執行回調,以及隊列和操作如何?我在哪裏可以利用GCD提供的線程機制?

希望這是明確的。

回答

8

這是一個很長的答案,但簡短的版本是,你正在做的事情是完全好的,並且線程安全,因爲你已經強制操作的重要部分在主線程上運行。

你的第一個問題是「如果我在NSOperationQueue中添加併發操作會發生什麼?」 As of iOS 4,NSOperationQueue在幕後使用GCD。當您的操作到達隊列的頂端時,它將被提交給GCD,GCD管理專用線程池,根據需要動態增長和收縮。 GCD指定其中一個線程來運行您的操作的方法,並保證此線程永遠不會成爲主線程。

start方法在併發操作中完成時,沒有什麼特別的事情發生(這是關鍵)。隊列將允許您的操作永遠運行,直到您將isFinished設置爲YES,並執行正確的KVO willChange/didChange調用,而不管調用線程如何。通常情況下,您會製作一個名爲finish的方法來做到這一點,它看起來像您一樣。

所有這一切都很好,但如果您需要觀察或操縱正在運行的操作的線程,則需要注意一些注意事項。需要記住的重要一點是:不要與GCD管理的線程混淆。您無法保證他們會超出當前的執行框架,並且您絕對不能保證隨後的代表調用(即從NSURLConnection)將在同一個線程上發生。事實上,他們可能不會。

在你的代碼示例中,你已經將start分流到主線程,所以你不必擔心很多後臺線程(GCD或其他)。當您創建NSURLConnection時,它會在當前運行循環中進行調度,並且它的所有委託方法都將在該運行循環的線程上被調用,這意味着在主線程上啓動連接可確保其委託回調也在主線程上發生。在這個意義上說,它是「線程安全的」,因爲除了操作本身的啓動之外,幾乎沒有任何事情發生在後臺線程上,這實際上可能是一個優點,因爲GCD可以立即回收線程並將其用於別的東西。

想象一下,如果您不強制start在主線程上運行,並且只使用GCD給您的線程,會發生什麼情況。如果線程消失,運行循環可能會永久掛起,比如當GCD將其回收到其私有池中時。有一些技術可以讓線程保持活躍狀態​​(例如添加一個空的NSPort),但它們不適用於由GCD創建的線程,僅適用於您自己創建的線程並且可以保證線程的生命週期。

這裏的危險是,在輕負載情況下,您實際上可以在GCD線程上運行一個運行循環,並認爲一切都很好。一旦開始運行許多並行操作,特別是如果您需要在midflight中取消它們,您將會看到從未完成且永不釋放內存泄漏的操作。如果您想要完全安全,您需要創建自己的專用NSThread並保持運行循環一直持續。

在現實世界中,更容易做你正在做的事情,只需在主線程上運行連接。管理連接消耗的CPU非常少,並且在大多數情況下不會干擾用戶界面,所以通過在後臺完全運行連接幾乎沒有什麼收穫。主線程的運行循環始終運行,您不需要惹惱它。

但是,使用上述專用線程方法可以完全在後臺運行NSURLConnection連接。例如,查看JXHTTP,特別是JXOperationJXURLConnectionOperation

+0

roland,首先,感謝您的回覆。這裏有很多概念。 +1爲您提供支持。我對使用併發操作和異步模式(如NSURLConnection)的方法存在一些疑問。如果在主線程中調用了'NSURLConnection'的回調函數,那麼使用兩者的優點是什麼?我怎樣才能利用* CDG *?看我的編輯。再次感謝你。 –

+1

'NSOperation'是封裝任何長期運行的可並行任務的自然方式(不管線程優勢如何)。 'NSURLConnection'通過它的委託方法(在哪裏放置響應數據,如何響應重定向等)爲你提供很多控制,但它們是異步的,所以如果你想在'NSOperation'中使用它們,這意味着它有是併發的(否則該操作將不會超過其「開始」方法的退出並繼續等待委託回調)。 – roland

+1

另一個主要優勢是,它允許您隨時取消連接(例如,如果您正在加載用戶剛剛解僱的視圖的圖像),這是通過Cocoa的其他連接技術無法實現的。你甚至可以重寫'NSOperation'的'cancel'方法並同時'取消'NSURLConnection'。 – roland