Запуск Python script из приложения Cocoa с использованием GCD

Я пытаюсь запустить Python script из приложения Cocoa. Он отлично работает в основном потоке, но я хотел бы, чтобы он работал в фоновом режиме в параллельной очереди GCD.

Я использую следующий метод для настройки класса менеджера, который запускает Python script:

- (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);
}

После чего вызов script вызван из следующего метода экземпляра, используя общий одноэлементный экземпляр класса менеджера:

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

Вышеупомянутый код Objective-C перехватывает следующий код 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)

Это работает, когда я всегда запускаю вышеуказанные методы Objective-C из основной очереди, но script возвращает null при запуске из любой другой очереди. Может ли кто-нибудь объяснить мне, что то, что я пытаюсь сделать, просто не поддерживается, и есть ли там хороший способ?

Сценарии Python вызывают часто и работают долго, поэтому выполнение этого основного потока будет слишком медленным, а будет запускать его из очереди. Кроме того, я хотел бы максимально содержать код concurrency внутри Objective-C.

Спасибо,

Ответы

Ответ 1

Из этой страницы, похоже, есть некоторые довольно сложные проблемы с потоками, специфичные для внедрения python. Есть ли причина, по которой вы не могли просто запускать эти скрипты в отдельном процессе? Например, следующий -runBunchOfScripts метод будет запускать script десять раз (вызывая -runPythonScript) в параллельной фоновой очереди, собирая результирующие выходы в массив строк и затем вызывая свой объект обратно в основной поток после завершения всех скриптов:

- (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...");
}

Естественно, что подход использования отдельных процессов имеет ограничения, не в последнюю очередь являющиеся жестким ограничением количества процессов, которые вы можете запускать, но он кажется намного менее опасным, чем внедрение всего интерпретатора. Я бы сказал, что, если вам не нужно взаимодействовать между сценариями и хостинговой средой, это будет лучшим подходом.