objective-c ios nsthread nsoperation nsoperationqueue

objective c - NSOperation-Forzar una operación para esperar a otros dinámicamente



objective-c ios (7)

Estoy tratando de implementar una cola de operaciones y tengo el siguiente escenario:

NSOperation A NSOperation B NSOperation C NSOperation D NSOperationQueue queue

Comienzo a agregar A a la queue .

Durante la ejecución de A necesito obtener 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 dependiendo de D

Para gestionar 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 3 o 4 NSOperations esperando y ejecutando el while(!operacao.isFinished && !self.isCancelled){} mi código se congela porque la NSOperation que es importante para mí no se ejecuta, incluso Si tiene mayor prioridad.

Lo que intenté

  • Agregando dependencia durante el tiempo de ejecución, pero como mi NSOperation ya se está ejecutando, no parece tener ningún efecto.

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

  • Puedo hacer algo parecido a while(!operacao.isFinished && !self.isCancelled){[NSThread sleepForTimeInterval:0.001];} . Funciona, pero ¿es esta la manera correcta? Tal vez haya una mejor solución.

En esta situación, ¿cómo puedo garantizar que la operación que deseo se ejecute y las demás esperarán 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 cola, es porque una operación necesitará la otra solo si se cumplen algunas condiciones. Sabré si necesito otra operación solo durante el tiempo de ejecución.

Gracias por tu tiempo.


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


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

Ejemplo 1: Uso de Grand Central Dispatch

GCD proporciona "grupos de despacho" ligeros, que le permiten ordenar tareas explícitamente y luego esperar a que se completen. En este caso, AlphaOperation crea un grupo y lo ingresa, luego inicia BetaOperation, cuyo completionBlock Bloquea hace que el grupo se quede. Cuando llama a dispatch_group_wait , el subproceso actual se bloquea hasta que el número de veces que ingresa al grupo es igual al número de veces que lo deja (es muy parecido a retener el conteo). No se olvide de verificar el estado de isCancelled de la operación 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 una NSOperationQueue local

Como ya está trabajando con operaciones, otra opción es crear una cola como propiedad de AlphaOperation, luego agregar BetaOperation y llamar a waitUntilAllOperationsAreFinished en la cola. Esto tiene un beneficio adicional ya que puede cancelar fácilmente las operaciones de la cola cuando se cancela AlphaOperation, 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


Como ha descubierto, realmente no puede hacer esto con las dependencias porque eso solo afecta cuando se inicia una operación, y si no sabe, necesitará las suboperaciones hasta que se ejecute la principal, por lo que no es bueno. No puede resolver este problema con una sola cola de operaciones.

Sin embargo, dado que ya se está ejecutando en una cola de operaciones, no es necesario agregar más operaciones a la cola. Solo ejecútelos de forma sincronizada en el lugar. Tienes que esperar a que regresen de todos modos, ¿por qué no?


Creo que está siguiendo un enfoque incorrecto. Si cada operación en la cola tiene una prioridad, y deben ejecutarse en orden, ¿por qué no usar 4 subprocesos diferentes?
Tome un ivar que represente el estado (0: no se completa ninguna operación, 1: se completa una operación, y así sucesivamente), protéjalo con una condición:

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

Initalizar todo (el estado comienza con cero), luego crear 4 hilos diferentes con diferentes prioridades. 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]; }

Así que todo se ejecuta en el orden que quieras.

EDITAR

Puedes hacer el equivalente que hice aquí, pero usando una 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 usar una cola con 1 como maxConcurrentOperationCount. La cola principal tiene este valor establecido en 1 y esa es la razón de sus problemas.


Intenta usar setCompletionBlock: esta manera:

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 establece un bloque de finalización en una operación, se ejecuta después de que la tarea principal de la operación se haya completado o cancelado. 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 cola.


Un enfoque es gestionar esto desde fuera de las clases de operación, es decir. configura las dependencias de operación entre A / B / C / D correctamente al crearlas.

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 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) Agregar las operaciones a la cola. La cola se ejecutará en función de las dependencias, es decir. D, C, B, A.


Una vez que una NSOperation está en su método principal, tienes que seguir adelante con ella. No hay estado de pausa, solo finalizado o cancelado.

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

Así que el proceso iría tal:

  • Crear la operación A, establecer delegado
  • no se puede proceder, delegar métodos de fuego.
  • el delegado crea una nueva operación B, crea una copia de la operación A, establece la dependencia de tal manera que A esperará la finalización de B
  • entonces el delegado cancela el original op A

Dentro del delegado, debe asegurarse de suspender la cola para evitar una condición de carrera. Después de los pasos anteriores, reanudar la cola. En la operación A, tendría varios lugares donde verifica que isCancelled no realice ningún trabajo adicional cuando se haya cancelado.