28

操作キューを実装しようとしていますが、次のシナリオがあります。

NSOperation A
NSOperation B
NSOperation C
NSOperation D
NSOperationQueue queue

に追加Aし始めqueueます。

の実行中にAデータを取得する必要があり、必要なものが返されるまでB続行できません。AB

に依存する場合とB依存する場合は、同じ状況が発生します。CCD

これを管理するために、それぞれNSOperationに次のコードがあります。

NSOperation *operation; //This can be A, B, C, D or any other NSOperation

[self setQueuePriority:NSOperationQueuePriorityVeryLow]; //Set the current NSOperation with low priority

[queue addOperation: operation]; //Add the operation that I want to the queue

while(!operation.isFinished && !self.isCancelled){} //I need to wait the operation that I depend before moving on with the current operation

[self setQueuePriority:NSOperationQueuePriorityNormal]; //After the while, the other operation finished so I return my priority to normal and continue

if(self.isCancelled){ //If I get out of the while because the current operation was cancelled I also cancel the other operation.
[operation cancel];          
}

私の問題は、3 つまたは 4 つのようなコードNSOperationsを待機して実行すると、while(!operacao.isFinished && !self.isCancelled){}優先度が高くても重要な NSOperation が実行されないため、フリーズすることです。

私が試したこと

  • 実行時に依存関係を追加しますが、私の NSOperation は既に実行されているため、何の効果もないようです。

  • 操作をキューに追加する代わりに、何かを行うことができます[operation start]。動作しますが、現在の操作をキャンセルすると、開始した他の操作もキャンセルされますか?

  • のようなことができますwhile(!operacao.isFinished && !self.isCancelled){[NSThread sleepForTimeInterval:0.001];}。それは機能しますが、これは正しい方法ですか?たぶん、より良い解決策があります。

この状況で、必要な操作が実行され、他の操作がバックグラウンドで待機することをどのように保証できますか? これを解決する正しい方法は何ですか?

キューを開始する前に依存関係を追加しない理由を誰かが私に質問した場合、それは、いくつかの条件が真の場合にのみ操作で他の依存関係が必要になるためです。実行時にのみ、他の操作が必要かどうかがわかります。

御時間ありがとうございます。

4

7 に答える 7

37

これは、不自然な例を使用した 2 つのアイデアです。ここでは 2 つの操作のみを使用しましたが、概念を任意の数に拡張したり、必要に応じてネストしたりできます。

例 1: グランド セントラル ディスパッチの使用

GCD は、軽量の「ディスパッチ グループ」を提供します。これにより、明示的にタスクを並べ替え、完了を待つことができます。この場合、AlphaOperation はグループを作成してそこに参加し、次に BetaOperation を開始completionBlockします。これにより、グループが離脱します。を呼び出すdispatch_group_waitと、現在のスレッドは、グループに入る回数がグループから出る回数と同じになるまでブロックされます (retain count によく似ています)。isCancelled長時間実行される可能性のあるタスクの後は、操作の状態を確認することを忘れないでください。

@interface BetaOperation : NSOperation
@end
@implementation BetaOperation
- (void)main
{
    NSLog(@"beta operation finishing");
}
@end

@interface AlphaOperation : NSOperation
@end
@implementation AlphaOperation
- (void)main
{
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);

    BetaOperation *betaOperation = [[BetaOperation alloc] init];
    betaOperation.completionBlock = ^{
        dispatch_group_leave(group);
    };

    [betaOperation start];

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    if ([self isCancelled])
        return;

    NSLog(@"alpha operation finishing");
}
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    dispatch_async(dispatch_get_main_queue(), ^{
        AlphaOperation *operation = [[AlphaOperation alloc] init];
        [operation start];
    });

    return YES;
}

@end

例 2: ローカル NSOperationQueue の使用

すでに操作を行っているので、AlphaOperation のプロパティとしてキューを作成し、次に BetaOperation を追加waitUntilAllOperationsAreFinishedしてキューを呼び出すという別のオプションがあります。これには、メソッドをオーバーライドするだけで、AlphaOperation がキャンセルされたときにキューの操作を簡単にキャンセルできるという追加の利点がありますcancel

@interface BetaOperation : NSOperation
@end
@implementation BetaOperation
- (void)main
{
    NSLog(@"beta operation finishing");
}
@end

@interface AlphaOperation : NSOperation
@property (strong) NSOperationQueue *queue;
@end
@implementation AlphaOperation
- (void)main
{
    self.queue = [[NSOperationQueue alloc] init];

    BetaOperation *betaOperation = [[BetaOperation alloc] init];
    [self.queue addOperation:betaOperation];
    [self.queue waitUntilAllOperationsAreFinished];

    if ([self isCancelled])
        return;

    NSLog(@"alpha operation finishing");
}

- (void)cancel
{
    [super cancel];

    [self.queue cancelAllOperations];
}
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    dispatch_async(dispatch_get_main_queue(), ^{
        AlphaOperation *operation = [[AlphaOperation alloc] init];
        [operation start];
    });

    return YES;
}

@end
于 2012-12-18T21:42:09.937 に答える
6

1つのアプローチは、これを操作クラスの外部から管理することです。A / B / C / D間の操作依存関係は、作成時に正しく設定してください。

手順:(これらの操作を作成しているメソッド内)

1)オペレーションAを作成します

2)オペレーションBによって提供されたデータが利用できない場合は、オペレーションBを作成し、オペレーションAをオペレーションBに依存させます。何かのようなものoperationA.addDependency(operationB);

3)。CとDについて手順2を繰り返します(つまり、必要に応じて、BはCに依存し、CはDに依存します)

4)操作をキューに追加します。キューは依存関係に基づいて実行されます。D、C、B、A。

于 2012-12-14T08:02:43.717 に答える
3

setCompletionBlock:次のように使用してみてください。

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperation *operationA;
NSOperation *operationB;

//... initialize operationA and operationB however you please ...

[operationA setCompletionBlock:^{
    if ([operationA satisfiesSomeCriteria]) {
        [queue addOperation:operationB];
    }
}];

[queue addOperation:operationA];

オペレーションに完了ブロックを設定すると、オペレーションのメイン タスクが完了またはキャンセルされた後に実行されます。したがって、操作が実行していた作業の結果を利用できるため、次の操作をキューに追加する必要があるかどうかを判断できます。

于 2012-12-17T01:03:59.577 に答える
2

間違ったアプローチに従っていると思います。キュー内のすべての操作に優先順位があり、順番に実行する必要がある場合、4 つの異なるスレッドを使用してみませんか?
状態を表す ivar (0: 操作が完了していない、1: 1 つの操作が完了しているなど) を取得し、次の条件で保護します。

@property(nonatomic,strong) NSCondition* condition;
@property (nonatomic) NSUInteger state; 

すべてを初期化 (状態はゼロから開始) し、異なる優先度を持つ 4 つの異なるスレッドを作成します。これは、スレッド A によって実行されるセレクターの例です。

- (void) threadA : (id) sender
{
    [condition lock];
    while(state!=3)
    {
        [condition wait];
    }
    // Do the job here
    state=4; // That's kinda useless but useful if in future you
    // want another thread that starts doing the job when A ends
    [condition unlock];
}

したがって、すべてが必要な順序で実行されます。

編集

ここで行ったのと同じことができますが、NSOperationQueue を使用します。

NSOperationQueue* queue=[NSOperationQueue new];
[queue setMaxConcurrentOperationCount: 4];
[queue addOperation: [[NSInvocationOperation alloc]initWithTarget: self selector: @selector(threadA:) object: nil]]

間違ったアプローチに従っているということは、maxConcurrentOperationCount として 1 のキューを使用すべきではないということです。メイン キューではこの値が 1 に設定されており、それが問題の原因です。

于 2012-12-18T00:58:27.883 に答える
2

NSOperation がメイン メソッドに入ったら、それを実行する必要があります。一時停止状態はなく、終了またはキャンセルのみです。

状態全体を新しいインスタンスにコピーする操作 A に NSCopying を実装します。操作 B からの情報が欠落しているため、この操作を実行できないことを伝えることができるデリゲート メソッドまたはブロックがあります。

したがって、プロセスは次のようになります。

  • オペレーション A を作成し、デリゲートを設定
  • 続行できません。デリゲート メソッドが起動します
  • デリゲートは新しい操作 B を作成し、操作 A のコピーを作成し、A が B の完了を待機するように依存関係を設定します。
  • その後、デリゲートは元の op A をキャンセルします

デリゲート内では、競合状態を回避するためにキューを中断する必要があります。上記の手順の後、キューを再開します。操作 A では、 isCancelled をチェックして、キャンセルされたときに main で実際にこれ以上作業を行わないようにする複数の場所があります。

于 2012-12-16T08:32:52.230 に答える
2

つまり、基本的には、次の作業を開始する前に、最初の作業が終了することを確認する必要があるだけですか? NSOperationQueue は、そうしないように指示しない限り、並行して実行されます。操作キューで setMaxConcurrentOperationCount: を呼び出して 1 に設定すると、基本的に、一度に 1 つの操作のみが実行されるシリアル キューに変換できます。

于 2012-12-07T11:54:19.587 に答える
1

あなたが見つけたように、依存関係でこれを実際に行うことはできません。これは、操作の開始時にのみ影響するためです。メイン操作が実行されるまでサブ操作が必要になることがわからない場合、それは良くありません。この問題は、単一の操作キューでは解決できません。

ただし、すでに操作キューで実行しているため、さらに操作をキューに追加する必要はありません。インプレースで同期的に実行するだけです。とにかく彼らが戻ってくるのを待たなければならないので、どうしてですか?

于 2012-12-16T09:48:46.137 に答える