2012-01-14 21 views
14

Estoy intentando ejecutar un script de Python desde una aplicación Cocoa. Funciona bien en el hilo principal, pero me gustaría que se ejecute en segundo plano, en una cola concurrente de GCD.Ejecutar script de Python desde la aplicación Cocoa usando GCD

estoy usando el siguiente método para configurar un gestor de la clase que se ejecuta la secuencia de comandos de Python:

- (BOOL)setupPythonEnvironment { 
    if (Py_IsInitialized()) return YES; 

    Py_SetProgramName("/usr/bin/python"); 
    Py_Initialize(); 

    NSString *scriptPath = [[NSBundle mainBundle] pathForResource:@"MyScript"  ofType:@"py"]; 

    FILE *mainFile = fopen([scriptPath UTF8String], "r"); 
    return (PyRun_SimpleFile(mainFile, (char *)[[scriptPath lastPathComponent] UTF8String]) == 0); 
} 

Luego de que el guión es (varias veces) se llama desde el siguiente método de instancia, mediante una instancia compartida de Singleton gerente de la clase:

- (id)runScriptWithArguments:(NSArray *)arguments { 
    return [NSClassFromString(@"MyScriptExecutor") runWithArguments:arguments]; 
} 

los anteriores Objective-C del código de ganchos en el siguiente código Python:

from Foundation import * 

def run_with_arguments(arguments): 
# ...a long-running script 

class MyScriptExecutor(NSObject): 
    @classmethod 
    def runWithArguments_(self, arguments): 
     return run_with_arguments(arguments) 

Esto funciona cuando siempre ejecuto los métodos Objective-C anteriores de la cola principal, pero el script devuelve nulo cuando se ejecuta desde cualquier otra cola. ¿Podría alguien explicarme si lo que estoy tratando de hacer simplemente no es compatible, y si hay una buena forma de evitarlo?

Las secuencias de comandos de Python se llaman a menudo y se ejecutan largas, por lo que hacer eso en el hilo principal sería demasiado lento, a lo estaría ejecutando desde una cola en serie. Además, me gustaría incluir el código de simultaneidad dentro de Objective-C tanto como sea posible.

Gracias,

+0

No hay suficiente información en este ejemplo para saber realmente lo que está haciendo. – jkh

+1

¿Me podría decir cómo integró el binario de Python con xcode? – bijan

Respuesta

11

De this page, parece que hay algunas preocupaciones de algunos de roscado bastante complejos específicos para la incorporación de pitón. ¿Hay alguna razón por la que no puedas ejecutar estos scripts en un proceso separado? Por ejemplo, el siguiente método -runBunchOfScripts ejecutaría la secuencia de comandos diez veces (llamando al -runPythonScript) en una cola de fondo paralela, recolectando las salidas resultantes en una matriz de cadenas y luego devolviendo la llamada al objeto una vez que todos los scripts hayan finalizado :

- (NSString*)runPythonScript 
{ 
    NSTask* task = [[[NSTask alloc] init] autorelease]; 
    task.launchPath = @"/usr/bin/python"; 
    NSString *scriptPath = [[NSBundle mainBundle] pathForResource:@"MyScript" ofType:@"py"]; 
    task.arguments = [NSArray arrayWithObjects: scriptPath, nil]; 

    // NSLog breaks if we don't do this... 
    [task setStandardInput: [NSPipe pipe]]; 

    NSPipe *stdOutPipe = nil; 
    stdOutPipe = [NSPipe pipe]; 
    [task setStandardOutput:stdOutPipe]; 

    NSPipe* stdErrPipe = nil; 
    stdErrPipe = [NSPipe pipe]; 
    [task setStandardError: stdErrPipe]; 

    [task launch];   

    NSData* data = [[stdOutPipe fileHandleForReading] readDataToEndOfFile]; 

    [task waitUntilExit]; 

    NSInteger exitCode = task.terminationStatus; 

    if (exitCode != 0) 
    { 
     NSLog(@"Error!"); 
     return nil; 
    } 

    return [[[NSString alloc] initWithBytes: data.bytes length:data.length encoding: NSUTF8StringEncoding] autorelease]; 
} 

- (void)runBunchOfScripts 
{ 
    dispatch_group_t group = dispatch_group_create(); 
    NSMutableArray* results = [[NSMutableArray alloc] init]; 
    for (NSUInteger i = 0; i < 10; i++) 
    { 
     dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
      NSString* result = [self runPythonScript]; 
      @synchronized(results) 
      { 
       [results addObject: result]; 
      } 
     }); 
    } 

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{ 
     [self scriptsDidFinishWithResults: results]; 
     dispatch_release(group); 
     [results release]; 
    }); 
} 

- (void)scriptsDidFinishWithResults: (NSArray*)results 
{ 
    NSLog(@"Do something with the results..."); 
} 

Naturalmente, el enfoque de utilizar procesos separados tiene sus limitaciones, no menos importante de los cuales es el límite duro en el número de procesos que puede iniciar, pero parece mucho menos cargado de peligros de incrustar la totalidad Interprete. Diría que, a menos que necesites interactuar de manera chata entre las secuencias de comandos y el entorno de alojamiento, este sería un mejor enfoque.

+0

Gracias, @ipmcc. Desafortunadamente, las secuencias de comandos tendrán que ejecutarse varias veces. Suficiente para golpear esa dura limitación demasiado pronto. Es por eso que esperaba configurar el entorno de Python una vez, dejando solo una sobrecarga mínima para cada vez que se ejecuta un script. De hecho, el script se envía al ejecutor de Python como un NSString, para ser evaluado utilizando una versión "segura" de 'eval'. –

+3

Puede hacer una solución similar con un NSTask; Inicie una (o n) larga tarea de intérprete de Python y haga que lea las secuencias de comandos de stdin y 'eval', y luego puede recopilar la salida de la salida estándar. Si puede interactuar con sus secuencias de comandos utilizando solo cadenas, esto aún tendría más sentido que incrustar el intérprete (al menos desde donde estoy sentado) – ipmcc

+0

Brillante. Eso definitivamente debería hacerlo! –

Cuestiones relacionadas