web-dev-qa-db-ja.com

NSManagedObjectContext performBlockAndWait:バックグラウンドスレッドで実行されませんか?

NSManagedObjectContextを次のように宣言しています。

- (NSManagedObjectContext *) backgroundMOC {
    if (backgroundMOC != nil) {
        return backgroundMOC;
    }
    backgroundMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    return backgroundMOC;
}

プライベートキューの同時実行タイプで宣言されているため、そのタスクはバックグラウンドスレッドで実行する必要があります。私は次のコードを持っています:

-(void)testThreading
{
    /* ok */
    [self.backgroundMOC performBlock:^{
        assert(![NSThread isMainThread]); 
    }];

    /* CRASH */
    [self.backgroundMOC performBlockAndWait:^{
        assert(![NSThread isMainThread]); 
    }];
}

performBlockAndWaitを呼び出すと、バックグラウンドスレッドではなくメインスレッドでタスクが実行されるのはなぜですか。

43
Snowman

別の答えを投げて、performBlockAndWaitが常に呼び出しスレッドで実行される理由を説明してください。

performBlockは完全に非同期です。常にブロックを受信MOCのキューにエンキューし、すぐに戻ります。したがって、

[moc performBlock:^{
    // Foo
}];
[moc performBlock:^{
    // Bar
}];

mocのキューに2つのブロックを配置します。これらは常に非同期で実行されます。不明なスレッドがブロックをキューから引き出して実行します。さらに、これらのブロックは独自の自動解放プールにラップされ、完全なCore Dataユーザーイベント(processPendingChanges)も表します。

performBlockAndWaitは内部キューを使用しません。これは、呼び出しスレッドのコンテキストで実行される同期操作です。もちろん、キューの現在の操作が実行されるまで待機してから、そのブロックは呼び出しスレッドで実行されます。これは文書化されています(そしていくつかのWWDCプレゼンテーションで再度主張されています)。

さらに、performBockAndWaitは再入可能であるため、ネストされた呼び出しはすべてその呼び出しスレッドで発生します。

Core Dataエンジニアは、キューベースのMOC操作が実行される実際のスレッドは重要ではないことを明確にしています。重要なのは、performBlock* AP​​Iを使用した同期です。

そのため、「performBlock」を「このブロックはキューに配置され、未確定のスレッド、未確定のスレッドで実行されます。関数は、キューに入れられるとすぐに呼び出し元に戻ります」

performBlockAndWaitは、「このブロックは、まったく同じスレッドで、不確定な時期に実行されます。このコードが完全に実行された後で、関数が返されます(このMOCに関連付けられている現在のキューが排出された後に発生します)。 」

[〜#〜]編集[〜#〜]

「performBlockAndWaitが内部キューを使用しない」ことを確認しますか?そうだと思います。唯一の違いは、performBlockAndWaitがブロックの完了まで待機することです。そして、スレッドを呼び出すとはどういう意味ですか?私の理解では、[moc performBlockAndWait]と[moc performBloc]はどちらも専用キュー(バックグラウンドまたはメイン)で実行されます。ここでの重要な概念はmocがキューを所有することであり、その逆ではありません。私が間違っていたら訂正してください。 – Philip007

自分で答えたのは間違いなので、私がしたように答えを言ったのは残念です。ただし、元の質問のコンテキストでは、それは正しいです。具体的には、プライベートキューでperformBlockAndWaitを呼び出すと、ブロックは関数を呼び出したスレッドで実行されます。キューには入れられず、「プライベートスレッド」で実行されません。

詳細に入る前に、ライブラリの内部動作に依存することは非常に危険であることを強調したいと思います。メインスレッドに関連付けられているものを除いて、特定のスレッドがブロックを実行することは決して期待できないということだけが重要です。したがって、メインスレッドでperformBlockAndWaitnotで実行されることを期待することは、それを呼び出したスレッドで実行されるため、お勧めできません。

performBlockAndWaitはGCDを使用しますが、独自のレイヤーも持っています(たとえば、デッドロックを防止するため)。 GCDコード(オープンソース)を見ると、同期呼び出しがどのように機能するかがわかります。一般に、呼び出しはキューと同期し、関数を呼び出したスレッドでブロックを呼び出します-キューがメインキューでない場合、またはグローバルキュー。また、WWDCの話では、Core Dataエンジニアは、performBlockAndWaitが呼び出しスレッドで実行されるという点を強調しています。

したがって、内部キューを使用しないと言っても、それはデータ構造をまったく使用しないという意味ではありません。呼び出しをすでにキューにあるブロック、および他のスレッドで送信されたブロックと他の非同期呼び出しと同期させる必要があります。ただし、performBlockAndWaitを呼び出すと、ブロックはキューに置かれません...代わりに、アクセスを同期して、関数を呼び出したスレッドで送信されたブロックを実行します。

現在、SOは、これよりも少し複雑なので、特にメインキューとGCDグローバルキューでは複雑なので、このフォーラムには適していません。ただし、後者はコアデータにとって重要ではありません。

重要な点は、performBlock*またはGCD関数を呼び出す場合、キューはスレッドではなく、メインキューだけが特定のスレッドでブロックを実行します。

コアデータperformBlockAndWaitを呼び出すと、ブロックは呼び出しスレッドで実行されます(ただし、キューに送信されたすべてのものと適切に同期されます)。

それがおそらくもっと混乱を引き起こしたかもしれませんが、私はそれが理にかなっていると思います。

[〜#〜]編集[〜#〜]

さらに、performBlockAndWaitが再入可能なサポートを提供する方法がFIFOブロックの順序付けを壊すという点で、これの暗黙の影響を見ることができます。例として...

[context performBlockAndWait:^{
    NSLog(@"One");
    [context performBlock:^{
        NSLog(@"Two");
    }];
    [context performBlockAndWait:^{
        NSLog(@"Three");
    }];
}];

FIFOキューの保証を厳密に遵守すると、ネストされたperformBlockAndWait( "Three")が非同期ブロック( "Two")の後に実行されることになります。非同期ブロックが送信された後に送信されます。ただし、それは不可能です。同じ理由で、ネストされたdispatch_sync呼び出しでデッドロックが発生します。同期を使用する場合は注意が必要なことバージョン。

一般に、dispatch_syncはデッドロックを引き起こす可能性があるため、可能な限り同期バージョンを避け、performBlockAndWaitなどの再入可能バージョンは、それをサポートするために「悪い」決定を下さなければなりません...同期バージョンはキューを「ジャンプ」します。

99
Jody Hagins

何故なの? Grand Central Dispatchのブロック同時実行パラダイム(MOCが内部で使用すると想定)は、開発者ではなく、ランタイムとオペレーティングシステムだけがスレッドを気にする必要があるように設計されています(OSは、より詳細な情報を得るよりもOSがより優れているためです) )。キューはスレッドと同じだと思っている人が多すぎます。ではない。

キューに入れられたブロックは、特定のスレッドで実行する必要はありません(メインキューのブロックである例外は、メインスレッドで実行する必要があります)。したがって、実際には、sync(つまり、performBlockAndWait)キューブロックは、ランタイムがスレッドを作成するよりも効率的であるとランタイムが感じる場合、メインスレッドで実行されます。とにかく結果を待っているので、メインスレッドが操作中にハングアップした場合でも、プログラムの機能は変わりません。

この最後の部分は、私が正しく覚えているかどうかはわかりませんが、GCDに関するWWDC 2011ビデオでは、ランタイムは同期操作のために、可能であればメインスレッドで実行しようとすることを述べたと思いますもっと効率的。結局のところ、「なぜ」に対する答えは、システムを設計した人しか答えられないと思います。

3
borrrden

performBlockAndWait:呼び出しは、並行性を導入しないようにコードを実行することのみを確認します(つまり、2つのスレッドでperformBlockAndWait:は同時に実行されず、互いにブロックされます)。

長い点と短い点は、MOC操作が実行されるスレッドに依存することはできないということです。 GCDを使用する場合、またはスレッドをまっすぐに処理する場合は、常に各操作に対してローカルMOCを作成し、それらをマスターMOCにマージする必要があるという難しい方法を学びました。

そのプロセスを非常に簡単にする素晴らしいライブラリ( MagicalRecord )があります。

0
piotrb

MOCがバックグラウンドスレッドを使用する義務があるとは思いません。 performBlock:またはperformBlockAndWait:を使用する場合、MOCでコードが同時実行の問題に遭遇しないようにする義務があります。 performBlockAndWait:は現在のスレッドをブロックすることになっているので、そのブロックをそのスレッドで実行するのが妥当と思われます。

0
Jesse Rusak