5

だから私は自分のサーバーに画像の配列を投稿しています。GCD を使用して配列を非同期的にポストしたいのですが、これが発生するメソッドを同期させて、単一の応答オブジェクトを返すことができるようにしたいと考えています。ただし、dispatch_group_wait メソッドはすぐに返されるようです (ブロックが終了するのを待っていません)。ブロック内でブロックを使用しているため、これは問題ですか?

NSArray *keys = [images allKeys];
__block NSMutableDictionary *responses = [NSMutableDictionary dictionaryWithCapacity:[images count]];

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < [keys count]; i++) {
    __block NSString *key = [keys objectAtIndex:i];
    dispatch_group_async(group, queue, ^{
        [self postImage:[images objectForKey:key] completionHandler:^(ServerResponse *response){
            @synchronized(responses){
                if ([response succeeded]) {
                    NSString *value = [[response data] objectForKey:@"image_token"];
                    [responses setObject:value forKey:key];
                    NSLog(@"inside success %@",responses);
                } else {
                    NSString *error = response.displayableError;
                    if (!error) {
                        error = @"Sorry something went wrong, please try again later.";
                    }
                    [responses setObject:error forKey:@"error"];
                    [responses setObject:response forKey:@"response"];
                }
            }
        }];
    });
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);

すべての [self postImage] メソッドがサーバーからコールバックし、応答辞書を変更するのを待つだけです。

4

3 に答える 3

9

ジョナサンのセマフォの例は的を射ています。ただし、代わりに条件変数を使用することについて言及したので、少なくとも例を投稿したいと思いました。一般に、CVは、N人のワーカー以外のより一般的な条件で待機するために使用できます。

条件変数にはその場所があることに注意してください(必ずしもここにある必要はありません)。通常、共有状態を変更するためにロックがすでに必要な場合に最適です。その後、他のスレッドは特定の条件を待つことができます。

NSUInteger numKeys = [keys count];

NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:numKeys];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSUInteger i = 0; i < numKeys; i++) {
    __block NSString *key = [keys objectAtIndex:i];
    dispatch_async(queue, ^{
        [self postImage:[images objectForKey:key] completionHandler:^(ServerResponse *response){
            // Basically, nothing more than a obtaining a lock
            // Use this as your synchronization primitive to serialize access
            // to the condition variable and also can double as primitive to replace
            // @synchronize -- if you feel that is still necessary
            [conditionLock lock];

            ...;

            // When unlocking, decrement the condition counter
            [conditionLock unlockWithCondition:[conditionLock condition]-1];
        }];
    });
}

// This call will get the lock when the condition variable is equal to 0
[conditionLock lockWhenCondition:0];
// You have mutex access to the shared stuff... but you are the only one
// running, so can just immediately release...
[conditionLock unlock];
于 2012-08-19T23:39:44.887 に答える
7

のコードを見ない-postImage:completionHandler:と、どこでスケジュールされているかを判断するのは難しいですが、iOS によって提供される何かを呼び出していると思います。その場合、ブロック内のハンドラー ブロックが非同期的にグローバル キューにディスパッチされ、iOS 提供の関数またはメソッドがすぐに返されます。ディスパッチ グループに関する限り、作業はほぼ瞬時に完了します。

呼び出しが行われるまでにまだスケジュールされていない作業をグループに待機させる簡単な方法はありませんdispatch_group_wait()ただし、セマフォと呼ばれる低レベルの Thingamajigger を追加して、アクションが正しい順序で完了するようにし、内部 (非同期) ブロックの範囲外でスケジュールすることができます。

NSUInteger numKeys = [keys count];

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSUInteger i = 0; i < numKeys; i++) {
    __block NSString *key = [keys objectAtIndex:i];
    dispatch_group_async(group, queue, ^{
        // We create a semaphore for each block here. More on that in a moment.
        // The initial count of the semaphore is 1, meaning that a signal must happen
        // before a wait will return.
        dispatch_semaphore_t sem = dispatch_semaphore_create(1);
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        [self postImage:[images objectForKey:key] completionHandler:^(ServerResponse *response){
            ...
            // This instructs one caller (i.e. the outer block) waiting on this semaphore to resume.
            dispatch_semaphore_signal(sem);
        }];

        // This instructs the block to wait until signalled (i.e. at the end of the inner block.)
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

        // Done with the semaphore. Nothing special here.
        dispatch_release(sem);
    });
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// Now all the tasks should have completed.
dispatch_release(group);

ただし、ここで問題があります。セマフォはカーネル リソースです。実行するタスクが 100 個あるのに、カーネルが 99 個のセマフォしか提供できないとしたら? Bad Things™ が発生します。1 つのセマフォのみを使用するようにコードを再構築できますが、それを待つのは少し不安定に見えます。ちなみに、これを行うと実際にはディスパッチ グループが完全に不要になるため、基本的にグループをセマフォに置き換えます。見よう!

NSUInteger numKeys = [keys count];

// set the count of the semaphore to the number of times it must be signalled before
// being exhausted. Up to `numKeys` waits will actually wait for signals this way.
// Additional waits will return immediately.
dispatch_semaphore_t sem = dispatch_semaphore_create(numKeys);
for (int i = 0; i < numKeys; i++) {
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSUInteger i = 0; i < numKeys; i++) {
    __block NSString *key = [keys objectAtIndex:i];
    dispatch_async(queue, ^{
        [self postImage:[images objectForKey:key] completionHandler:^(ServerResponse *response){
            ...;

            // This decrements the semaphore's count by one. The calling code will be
            // woken up by this, and will then wait again until no blocks remain to wait for.
            dispatch_semaphore_signal(sem);
        }];
    });
}

// At this point, all the work is running (or could have already completed, who knows?).
// We don't want this function to continue running until we know all of the blocks
// have run, so we wait on our semaphore a number of times equalling the number
// of signals we expect to get. If all the blocks have run to completion before we've
// waited for all of them, the additional waits will return immediately.
for (int i = 0; i < numKeys; i++) {
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
// Now all the tasks should have completed.
dispatch_release(sem);
于 2012-08-19T22:32:20.703 に答える
3

はい、述べられているように、呼び出しが非同期のように見えるdispatch_group_wait()ため、待機していません。postImage:completionHandler:そして、このコードブロックを同期的に実行することが本当に必要な場合は、セマフォまたはロックが適切なソリューションのようです。

ただし、すべての応答を 1 つの辞書に集めて処理したい場合は、GCD を最大限に活用するのが最も適切な解決策だと思います。また、ディスパッチ キューを使用して変更可能なディクショナリを管理します。これは、私が見たほとんどのドキュメントでアップルが好んでいるソリューションのようです。

ソリューションの要点は、変更可能なディクショナリの所有権を基本的に単一のキューに転送し、そのキューからのみ変更することです。私が言及する「所有権」は、メモリ管理の意味でのオブジェクトの所有権ではなく、意味での変更権の所有権です。

私はこのようなことを検討します:

NSArray *keys = [images allKeys];
// We will not be reasigning the 'responses' pointer just sending messages to the NSMutableDictionary object __block is not needed.
NSMutableDictionary *responses = [NSMutableDictionary dictionaryWithCapacity:[images count]];
// Create a queue to handle messages to the responses dictionary since mutables are not thread safe.
// Consider this thread the owner of the dictionary.
dispatch_queue_t responsesCallbackQueue = dispatch_queue_create("com.mydomain.queue", DISPATCH_QUEUE_SERIAL);

for (NSString *key in keys) {
    // This async call may not be needed since postImage:completionHandler: seems to be an async call itself.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
        [self postImage:[images objectForKey:key] completionHandler:^(ServerResponse *response){
            dispatch_async(responsesCallbackQueue, ^{
                [responses setObject:response forKey:key];
                // Perhaps log success for individual requests.
                if (responses.count == keys.count){
                    NSLog(@"All requests have completed");
                    // All responses are available to you here.
                    // You can now either transfer back 'ownership' of the dictionary.
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [self requestsHaveFinished:responses];
                    });
                }
            });
        }];
    });
}
于 2012-08-20T00:44:01.917 に答える