2

私は GCD を使用して、画像操作などの重い作業を行っています。多くの場合、3 つまたは 4 つのタスクを同時に実行しています。

これらのタスクの中には、他のタスクよりも早く完了するものがあります。シリアル キューを使用せずに、コールバックが正しい元の順序で起動されるようにするにはどうすればよいですか?

例えば:

  • タスク 1 の所要時間は 1 秒です
  • タスク 2 は 5 秒かかります
  • タスク 3 には 2 秒かかります

さまざまな計算時間にもかかわらず、1、2、3 の最終コールバック順序を確保するにはどうすればよいですか?

// self.queue = dispatch_queue_create("com.example.queue", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(self.queue, ^{
    // Long-running code here of varying complexity

    dispatch_async(dispatch_get_main_queue(), ^{
        // Callback here
    });
});

編集:

コメントによると、残りのタスクが処理中であっても、タスク 1 が完了するとすぐに最初の通知が送信されます。タスク 3 が完了すると、タスク 2 が完了するまで保持され、最初にタスク 2 と 3 の通知が立て続けに送信されます。

タスクをプッシュおよびシフトするためのある種の可変配列が機能すると考えています。しかし、よりクリーンな方法はありますか?

4

6 に答える 6

12

それぞれの完了ブロック (最初のブロックを除く) には、2 つの依存関係があります。重量物を持ち上げるジョブと、前の重量物を持ち上げるジョブの完了ブロックです。

GCD を直接使用する代わりに、GCD を使用NSOperationQueueして要件を満たす方がはるかに簡単です。NSBlockOperationNSOperationQueueGCDの上に構築されています。)

操作キューと、前の完了操作への参照が必要です。

@property (nonatomic, strong) NSOperationQueue *queue;
@property (nonatomic, strong) NSOperation *priorCompletionOperation;

を に初期化queueNSOperationQueueます。priorCompletionOperation最初のジョブを取得するまで nil のままにします。

次に、操作をキューに送信する前に、依存関係を設定するだけです。

    NSBlockOperation *heavyLifting = [NSBlockOperation blockOperationWithBlock:^{
        // long-running code here of varying complexity
    }];

    NSBlockOperation *completion = [NSBlockOperation blockOperationWithBlock:^{
        // Callback here
    }];

    [completion addDependency:heavyLifting];
    if (self.priorCompletionOperation) {
        [completion addDependency:self.priorCompletionOperation];
    }

    [self.queue addOperation:heavyLifting];
    [[NSOperationQueue mainQueue] addOperation:completion];

    self.priorCompletionOperation = completion;

このジョブ キューイング コードは、一度に 1 つのスレッドからのみ実行する必要があることに注意してください。自動的に発生するメイン スレッド (またはメイン キュー) からのみジョブをキューに入れる場合。

于 2013-07-16T09:16:49.427 に答える
4

したがって、最終的な解決策は非常に簡単でした。

私は NSOperation のシンプルさが気に入っていますが、前の反復のキューへの参照を保持しなければならない、またはそのようなタイトなループで NSOperations を作成および破棄する必要があるという考えは好きではありませんでした。

最終的に、GCD/Semaphore モデルのバリエーションを選択しました。

// self.serialQueue = dispatch_queue_create("com.example.queue", NULL);
// self.conQueue = dispatch_queue_create("com.example.queue", DISPATCH_QUEUE_CONCURRENT);

// Create a variable that both blocks can access, so we can pass data between them
__block id *message = nil;

// Create semaphore which will be used to lock the serial/callback queue
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

// This is effectively the 'callback' that gets fired when the semaphore is released
dispatch_async(self.serialQueue, ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    if (message) {
        NSLog(@"Callback message: %@", message);
    }
});

// Do the heavy lifting, then release the semaphore which allows the callback to fire
dispatch_async(self.conQueue, ^{

    // ... Heavy lifting here

   message = [NSString stringWithFormat:@"Success!"];
   dispatch_semaphore_signal(semaphore);
});
于 2013-07-17T10:45:49.203 に答える
1

画像処理の同時セットとアップロード処理のシリアル セットの 2 つの異なるものがあります。を作成NSOperationしてアップロード処理を処理し、順序どおりに実行する必要があるようにそれらの間の依存関係を設定できます。操作は、開始された後 (セマフォ)、アップロードするデータを受け取るまで待機します。そのデータは、同時 GCD タスクによって提供されます。

于 2013-07-16T09:03:06.467 に答える
1

シリアル キューを使用せずに、コールバックが正しい元の順序で起動されるようにするにはどうすればよいですか?

そうではありません-コールバックはタスクがいつ終了したかを通知し、キューに入れられた順序とは異なる順序で終了した場合、コールバックは完了順に起動します。

ただし、違いは、完了通知の受信を処理する方法です。join3 つのワーカー スレッドで実行しているスレッドの問題と同じように扱うことができます。1 つ目、2 つ目、3 つ目が完了するまで待ちます。このために、それぞれが完了すると生成されるセマフォまたはミューテックス (またはアトミック カウンター) を使用できます。

このように、ハンドラー コードは完了の順序をあまり気にしません。各タスクが順番に完了するまで待機するだけです。タスクがすでに終了している場合は、待つ必要はありません。待っている間に、いつでも他のことをすることもできます。

于 2013-07-16T08:44:55.797 に答える
1

コメントで述べたように、最も簡単な解決策は、最終タスクが完了したときにのみコールバックを実行することです。その後、すべてのタスクのコールバック コードを順番に呼び出すことができます。つまり、必要に応じてオブジェクトを配列に追加し、コールバックのインクリメンタル カウンターが配列内のオブジェクト数に達したときに、コールバック コードを実行します。

コールバックがオブジェクトに依存しない場合は、単純にカウンターを使用して、完了後にコードを実行できます。競合状態を回避するために、メイン スレッドまたは '@sync()' ディレクティブでもカウンター操作を実行することを忘れないでください。

dispatch_async(dispatch_get_main_queue(), ^(void){ /* code*/ });

編集:完了ハンドラーで同じ配列手法を使用して、オブジェクトのフラグを送信準備完了として設定します。次に、配列を可能な限りトラバースし、準備ができているすべてのオブジェクトを順番に送信します。それ以外の場合は、停止して次の完了ハンドラ呼び出しを待ちます。カウンターを使用して、位置を追跡したり、完了時に配列から項目を削除したりできますが、必ずメインスレッドまたは同期ブロックで実行してください。

@interface MyImage : NSImage
@property (assign) BOOL ready;
@end


@implementation MyImage

@synthesize ready;

- (void)send {
    //Send image, make sure it's threaded send, NSConnection should be okay
}
@end


 NSArray *imagesNeededToSend = [NSMutableArray arrayWithObjects:image1, image2, image3, nil];

dispatch_async(self.queue, ^{
    // Long-running code here of varying complexity

    dispatch_async(dispatch_get_main_queue(), ^{
        self.ready = YES;
        dispatch_async(dispatch_get_main_queue(), ^(void){ [self sendImages] });
    });
});

...

- (void)sendImages {
    MyImage *firstImage = [imagesNeededToSend firstObject]; //Xcode Beta, but you should have a a category for firstObject, very useful.
   if (firstImage.ready) {
       [firstImage send];
       [imagesNeededToSend removeObjectAtIndex:0];
       [self sendImages];
   }
}
于 2013-07-16T08:52:14.847 に答える