web-dev-qa-db-ja.com

NSOperation-操作に他のユーザーを動的に待機させる

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

_NSOperation A
NSOperation B
NSOperation C
NSOperation D
NSOperationQueue queue
_

Aqueueに追加し始めます。

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

Bに依存するCCに依存するDにも同じ状況が発生します。

これを管理するために、各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];}のようなことができます。動作しますが、これは正しい方法ですか?多分より良い解決策があります。

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

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

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

27
Rafael

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

例1:Grand Central Dispatchの使用

GCDは、軽量の「ディスパッチグループ」を提供します。これにより、タスクを明示的に順序付けし、タスクの完了を待つことができます。この場合、AlphaOperationはグループを作成してそれを入力し、BetaOperationを開始します。そのcompletionBlockによってグループが離脱します。 dispatch_group_waitを呼び出すと、現在のスレッドは、グループに入る回数がグループから出る回数と同じになるまでブロックされます(保持カウントによく似ています)。長時間実行される可能性のあるタスクの後で、操作の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を呼び出すことです。これには、cancelメソッドをオーバーライドするだけで、AlphaOperationがキャンセルされたときにキューの操作を簡単にキャンセルできるという追加の利点があります。

@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
37
roland

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。

6
user1885297

次のように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];

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

3
Adam

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

2
Max

あなたは間違ったアプローチに従っていると思います。キュー内のすべての操作に優先順位があり、それらを順番に実行する必要がある場合は、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に設定されており、それが問題の原因です。

2
Ramy Al Zuhouri

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

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

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

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

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

2
Cocoanetics

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

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

1
jrturton