NSOperation: Forzar una operación para esperar dinámicamente a los demás.

Estoy intentando implementar una queue de operaciones y tengo el siguiente escenario:

NSOperation A NSOperation B NSOperation C NSOperation D NSOperationQueue queue 

Empiezo a agregar A a la queue .

Durante la ejecución de A necesito get algunos datos de B y no puedo continuar con A hasta que B devuelva lo que necesito.

La misma situación ocurrirá para B dependiendo de C y para C según D

Para administrar esto, en cada NSOperation tengo este código:

 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]; } 

Mi problema es que cuando tengo algo así como 3 o 4 NSOperations esperando y ejecutando el while(!operacao.isFinished && !self.isCancelled){} mi código simplemente se congela porque el NSOperation que es importante para mí no se ejecuta, ni siquiera si tiene mayor prioridad

Lo que intenté

  • Agregar dependencia durante el time de ejecución, pero dado que mi NSOperation ya se está ejecutando, no parece tener ningún efecto.

  • En lugar de agregar la operación a la queue, puedo hacer algo [operation start] . Funciona, pero ¿cancelar la operación actual también cancelará las otras operaciones que comencé?

  • Puedo hacer algo así como while(!operacao.isFinished && !self.isCancelled){[NSThread sleepForTimeInterval:0.001];} . Funciona, pero ¿es esta la manera correcta? Quizás haya una mejor solución.

En esta situación, ¿cómo puedo garantizar que la operación que deseo se ejecutará y que los demás esperen en segundo plano? ¿Cuál es la forma correcta de resolver esto?

Si alguien me pregunta por qué no agrego la dependencia antes de comenzar mi queue, porque una operación necesitará la otra solo si algunas condiciones son verdaderas. Lo sabré si necesito otra operación solo durante el time de ejecución.

Gracias por tu time.

Aquí hay dos ideas para ti con ejemplos artificiales. Solo usé dos operaciones, pero podría ampliar el concepto a cualquier número y / o anidarlas según sea necesario.

Ejemplo 1: Uso de Grand Central Dispatch

GCD proporciona "grupos de envío" ligeros, que le permiten orderar de forma explícita las tareas y esperar a que se completen. En este caso AlphaOperation crea un grupo y lo ingresa, luego inicia BetaOperation, cuyo completionBlock hace que el grupo se quede. Cuando llama a dispatch_group_wait , el subprocess actual bloquea hasta que el número de veces que ingresa al grupo es igual al número de veces que lo abandona (muy parecido a retener el recuento). No olvide verificar el estado de la operación isCancelled después de cualquier tarea potencialmente larga.

 @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 

Ejemplo 2: uso de un NSOperationQueue local

Ya que ya tiene operaciones de trabajo, otra opción es crear una queue como propiedad de AlphaOperation, luego agregar BetaOperation y llamar a waitUntilAllOperationsAreFinished en la queue. Esto tiene un beneficio adicional ya que puede cancelar fácilmente las operaciones de la queue cuando AlphaOperation se cancela, simplemente anulando el método de 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 

Un enfoque es administrar esto desde fuera de las classs de operación, es decir. configure las dependencies de operación entre A / B / C / D correctamente mientras las crea.

Pasos: (En el método que está creando estas operaciones)

1) Crear la operación A

2) Si los datos proporcionados por la Operación B no están disponibles, cree la Operación B y haga que la Operación A dependa de la Operación B. es decir. algo así como operationA.addDependency(operationB);

3) repita el paso 2 para C y D. (es decir, B depende de C y C depende de D, si es necesario)

4) Agregue las operaciones a la queue. La queue se ejecutará en function de las dependencies, es decir. D, C, B, A.

Prueba usar setCompletionBlock: así:

 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]; 

Cuando configura un bloque de finalización en una operación, se ejecuta después de completar o cancelar la tarea principal de la operación. Por lo tanto, los resultados del trabajo que estaba ejecutando la operación están disponibles para que pueda decidir si la próxima operación debe agregarse a la queue.

¿Entonces básicamente necesitas asegurarte de que el primero termine antes de comenzar el siguiente? NSOperationQueue se ejecutará en paralelo a less que le diga que no lo haga. Puede llamar a setMaxConcurrentOperationCount: en su queue de operaciones y configurarlo en uno para básicamente convertirlo en una queue en serie en la que solo se ejecutará una operación a la vez.

Una vez que NSOperation se encuentra en su método principal, debe seguirlo. No hay un estado pausado, solo terminado o cancelado.

Implementaría un NSCopying en la operación A, que copyría todo el estado en una nueva instancia. Tendría un método o bloque delegado que puede comunicar que esta operación no puede pasar porque falta información de la operación B.

Entonces el process sería así:

  • Crear operación A, establecer delegado
  • no puede proceder, delegar el método de incendios
  • el delegado crea una nueva operación B, crea una copy de la operación A, establece la dependencia tal que A esperará la finalización de B
  • entonces el delegado cancela la operación original A

Dentro del delegado, debes asegurarte de suspender la queue para evitar una condición de carrera. Después de los pasos anteriores, reanuda la queue. En la operación A, usted tendría varios lugares en los que comtesting si está cancelado para que en realidad no haga más trabajo en la networking principal cuando se haya cancelado.

Creo que estás siguiendo un enfoque equivocado. Si todas las operaciones en la queue tienen prioridad y deben ejecutarse en order, ¿por qué no usar 4 hilos diferentes?
Tome un ivar que represente el estado (0: no se ha completado ninguna operación, 1: una operación se completó, etc.), protéjalo con una condición:

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

Initalice todo (el estado comienza con cero), luego cree 4 hilos diferentes con prioridades diferentes. Este es un ejemplo para el selector ejecutado por el hilo 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]; } 

Entonces, todo se ejecuta en el order que desee.

EDITAR

Puedes hacer el equivalente que hice aquí, pero usando un NSOperationQueue:

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

Al decir que está siguiendo el enfoque equivocado, quiero decir que no debe utilizar una queue con 1 como maxConcurrentOperationCount. La queue principal tiene este valor establecido en 1 y esa es la razón de sus problemas.

Como has encontrado, realmente no puedes hacer esto con las dependencies porque eso solo afecta cuando se inicia una operación, y si no sabes que necesitarás las suboperaciones hasta que la principal se esté ejecutando, eso no sirve. No puede resolver este problema con una única queue de operaciones.

Sin embargo, dado que ya se está ejecutando en una queue de operaciones, no hay necesidad de agregar otras operaciones a una queue. Simplemente ejecútelos sincrónicamente en el lugar. Tienes que esperar a que regresen de todos modos, entonces ¿por qué no?