使用GCD从Cocoa应用程序运行Python脚本

Bas*_*erd 15 python objective-c grand-central-dispatch

我正在尝试从Cocoa应用程序运行Python脚本.它在主线程上运行得很好,但是我希望它在后台运行,并发GCD队列.

我正在使用以下方法来设置运行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);
}
Run Code Online (Sandbox Code Playgroud)

之后,使用manager类的共享单例实例从以下实例方法(重复)调用脚本:

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

上面的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)
Run Code Online (Sandbox Code Playgroud)

当我总是从主队列运行上述Objective-C方法时,这可以工作,但是当从任何其他队列运行时,脚本返回null.有人可以解释一下,如果我想要做的事情不被支持,是否有一个好的方法呢?

经常调用Python脚本并运行很长时间,因此在主线程上执行此操作会太慢,而是从串行队列中运行它.另外,我想尽可能地在Objective-C中包含并发代码.

谢谢,

ipm*_*mcc 13

这个页面中,看起来有一些非常复杂的线程问题特定于嵌入python.您是否有理由不能在单独的进程中运行这些脚本?例如,以下-runBunchOfScripts方法将-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...");
}
Run Code Online (Sandbox Code Playgroud)

当然,使用单独进程的方法有其局限性,其中最重要的是对可以启动的进程数量的硬性限制,但是与嵌入整个解释器相比,它似乎不那么危险.我会说除非你需要在脚本和托管环境之间闲聊,否则这将是一种更好的方法.

  • 您可以使用NSTask执行类似的解决方案; 启动一个(或n个)长期运行的Python解释器任务,让它从stdin和`eval`读取脚本,然后你可以从标准输出中收集输出.如果你只使用字符串可以与你的脚本进行交互,那么这比嵌入解释器更有意义(至少从我坐的位置开始). (3认同)