3

これは 2 つの部分からなる質問です。誰かが完全な回答で返信できることを願っています。

NSOperationは強力なオブジェクトです。これらは、非同時または同時の 2 つの異なるタイプにすることができます。

最初のタイプは同期的に実行されます。に追加することで、非同時操作を利用できますNSOperationQueue。後者はスレッドを作成します。結果は、その操作を同時に実行することになります。唯一の注意点は、このような操作のライフサイクルに関するものです。そのmainメソッドが終了すると、キューから削除されます。これは、非同期 API を扱う場合に問題になる可能性があります。

では、同時操作についてはどうでしょうか。アップルのドキュメントから

並行操作 (つまり、呼び出しスレッドに対して非同期で実行される操作) を実装する場合は、追加のコードを記述して、操作を非同期で開始する必要があります。たとえば、別のスレッドを生成したり、非同期システム関数を呼び出したり、その他のことを行って、start メソッドがタスクを開始し、すぐに、おそらくタスクが終了する前に戻るようにすることができます。

これは私にはほとんど明らかです。それらは非同期で実行されます。ただし、確実に実行できるように適切なアクションを実行する必要があります。

私には明らかでないことは、次のとおりです。Doc 言います:

注: OS X v10.6 では、操作キューは isConcurrent によって返された値を無視し、常に別のスレッドから操作の start メソッドを呼び出します。

それは本当にどういう意味ですか?に同時操作を追加するとどうなりNSOperationQueueますか?

次に、この投稿のConcurrent OperationsNSURLConnectionでは、同時操作を使用して(非同期形式で) 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 が提供するスレッド化メカニズムはどこで利用できますか?

これが明確であることを願っています。

4

1 に答える 1

9

これは一種の長い答えですが、短いバージョンでは、操作の重要な部分をメインスレッドで実行するように強制したため、実行していることは完全に問題なくスレッドセーフです。

最初の質問は、「NSOperationQueue に同時操作を追加するとどうなるか?」というものでした。iOS 4 の時点でNSOperationQueue、 GCD がバックグラウンドで使用されます。操作がキューの先頭に達すると、GCD に送信されます。GCD は、必要に応じて動的に拡大および縮小するプライベート スレッドのプールを管理します。GCD は、これらのスレッドの 1 つを割り当てstartて操作のメソッドを実行し、このスレッドがメイン スレッドになることは決してないことを保証します。

メソッドが並行操作で終了するstartと、特別なことは何も起こりません (これがポイントです)。キューを使用すると、呼び出し元のスレッドに関係なく、適切な KVO willChange/didChange 呼び出しを設定isFinishedして実行するまで、操作を永久に実行できます。YES通常finish、それを行うために呼び出されるメソッドを作成しますが、それはあなたが持っているように見えます。

これはすべて問題ありませんが、操作が実行されているスレッドを監視または操作する必要がある場合は、いくつかの注意事項があります。覚えておくべき重要なことは、 GCD によって管理されるスレッドを台無しにしないことです。それらが現在の実行フレームを過ぎても存続することを保証することはできません。また、後続のデリゲート呼び出し (つまり、 からNSURLConnection) が同じスレッドで発生することを確実に保証することもできません。実際、おそらくそうではないでしょう。

コード サンプルでは、start​​バックグラウンド スレッド (GCD など) についてあまり心配する必要がないように、メイン スレッドに切り替えています。を作成するNSURLConnectionと、現在の実行ループでスケジュールされ、すべてのデリゲート メソッドがその実行ループのスレッドで呼び出されます。つまり、メイン スレッドで接続を開始すると、そのデリゲート コールバックがメイン スレッドでも発生することが保証されます。この意味で、操作自体の開始以外にバックグラウンド スレッドでは実際にはほとんど何も起こっていないため、「スレッド セーフ」です。これは、GCD がスレッドをすぐに回収して別の目的に使用できるため、実際には利点となる可能性があります。

start強制的にメイン スレッドで実行せず、GCD から提供されたスレッドを使用した場合にどうなるか想像してみましょう。実行ループは、GCD によってプライベート プールに再利用される場合など、そのスレッドが消えると、永久にハングする可能性があります。スレッドを存続させるためのいくつかの手法 (空の追加などNSPort) が浮かんでいますが、それらは GCD によって作成されたスレッドには適用されず、自分で作成したスレッドにのみ適用され、寿命を保証できます。

ここでの危険は、負荷が軽い場合、実際には GCD スレッドで実行ループを実行することから逃れ、すべて問題ないと考えてしまうことです。多くの並列操作の実行を開始すると、特にそれらを途中でキャンセルする必要がある場合、操作が完了せず、割り当てが解除されず、メモリ リークが発生することがわかります。完全に安全になりたい場合は、独自の専用を作成しNSThread、実行ループを永久に維持する必要があります。

現実の世界では、メイン スレッドで接続を実行するだけで、現在行っていることを実行する方がはるかに簡単です。接続の管理はほとんど CPU を消費せず、ほとんどの場合 UI に干渉しないため、接続を完全にバックグラウンドで実行しても得られるものはほとんどありません。メインスレッドの実行ループは常に実行されており、いじる必要はありません。

NSURLConnectionただし、上記の専用スレッド方式を使用して、接続を完全にバックグラウンドで実行することは可能です。例として、JXHTTP、特にクラスJXOperationおよびJXURLConnectionOperationを確認してください

于 2013-01-11T23:51:17.450 に答える