在传递大量数据时解决NSFileHandle NSTask阻塞问题

时间:2009-09-08 17:37:28

标签: cocoa macos nstask

我有一些处理从我的应用程序导出数据的代码。它接收一个充满XML的NSString,并通过PHP脚本运行它来生成HTMl,RTF等。除非用户有一个大的列表,否则它运行良好。这显然是因为它超过了8k左右的NSPipe缓冲区。

我在readPipe和readHandle中解决了它(我认为),但我不确定如何在writeHandle / writePipe中处理它。该应用程序将在[writeHandle writeData:[in...进行竞技,除非我在gdb中打破它,等待几秒然后继续。

有关如何在我的代码中解决此问题的任何帮助?

- (NSString *)outputFromExporter:(COExporter *)exporter input:(NSString *)input {
  NSString *exportedString = nil;
  NSString *path = [exporter path];
  NSTask *task = [[NSTask alloc] init];

  NSPipe *writePipe = [NSPipe pipe];
  NSFileHandle *writeHandle = [writePipe fileHandleForWriting];
  NSPipe *readPipe = [NSPipe pipe];
  NSFileHandle *readHandle = [readPipe fileHandleForReading];

  NSMutableData *outputData = [[NSMutableData alloc] init];
  NSData *readData = nil;

  // Set the launch path and I/O for the task
  [task setLaunchPath:path];
  [task setStandardInput:writePipe];
  [task setStandardOutput:readPipe];

  // Launch the exporter, it will convert the raw OPML into HTML, Plaintext, etc
  [task launch];

  // Write the raw OPML representation to the exporter's input stream
  [writeHandle writeData:[input dataUsingEncoding:NSUTF8StringEncoding]];
  [writeHandle closeFile];

  while ((readData = [readHandle availableData]) && [readData length]) {
    [outputData appendData:readData];
  }

  exportedString = [[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding];
  return exportedString;
}

6 个答案:

答案 0 :(得分:11)

自10.7以来有一个新API,因此您可以避免使用NSNotifications。

task.standardOutput = [NSPipe pipe];
[[task.standardOutput fileHandleForReading] setReadabilityHandler:^(NSFileHandle *file) {
    NSData *data = [file availableData]; // this will read to EOF, so call only once
    NSLog(@"Task output! %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);

    // if you're collecting the whole output of a task, you may store it on a property
    [self.taskOutput appendData:data];
}];

您可能希望对task.standardError重复上述内容。

重要:

当你的任务终止时,你必须将readabilityHandler块设置为nil;否则,您将遇到高CPU使用率,因为读数永远不会停止。

[task setTerminationHandler:^(NSTask *task) {

    // do your stuff on completion

    [task.standardOutput fileHandleForReading].readabilityHandler = nil;
    [task.standardError fileHandleForReading].readabilityHandler = nil;
}];

这都是异步的(你应该做异步),所以你的方法应该有^完成块。

答案 1 :(得分:4)

NSFileHandle availableData似乎无限地阻止:

我做了一个测试程序,我从另一个程序用NSTask调用。我将NSFileHandle分配给stdin并从管道读取数据。测试程序使用NSLog函数向stdout充斥大量文本。似乎没有办法解决它,无论我使用的NSFileHandle中的哪个API,迟早可用的数据块,然后应用程序将无限地挂起并且什么都不做。它实际上停在数据读取语句中,无论它是放在while还是在其中。我试着一个接一个地读取字节,但没有帮助 之一:

data = [file readDataOfLength: 1];  // blocks infinitely

data = [file availableData]; // blocks infinitely

这种情况有效,直到它也冻结了。似乎我注意到NSFileHandle API并不真正适用于输出大量数据的shell命令,因此我必须使用Posix API来解决这个问题。

如何使用此API从Stack Overflow或Internet中的其他站点(无论是同步还是异步)读取部分数据的每个示例似乎都会阻止持续读取fileAvailableData。

答案 2 :(得分:1)

简单而痛苦的事实是,将大量数据写入子进程然后从中读取大量数据并不是您可以在单个函数或方法中执行的操作而不会阻止UI。

解决方案同样简单,当然是一个痛苦的潜在客户:使导出异步。尽可能写入数据,并尽可能读取数据。您不仅不会阻止UI,还可以更新真正长时间导出的进度指示器,并且可以并行执行多个导出(例如,从单独的文档中)。

这是有效的,但UI收益很大,结果是内部和外部的设计更清晰。

答案 3 :(得分:0)

要清楚,这不仅是缓慢的,而且在您使用调试器打破之前它实际上已经冻结了吗?你的子流程不是问题吗?

可以预期NSFileHandle会处理您向其投放的任何数据,但也许您可以使用-subdataWithRange:将数据拆分为较小的块,以查看其产生的影响。您还可以获取fileDescriptor并使用POSIX API(fdopen,fwrite等)写入流。如果确实如此,POSIX API将提供更大的灵活性。

答案 4 :(得分:0)

我相信发生的事情是我遇到了由[writeHandle writeData:[input dataUsingEncoding:NSUTF8StringEncoding]];行填充溢出缓冲区并导致应用程序挂起直到它(从未)清空所导致的死锁。

我通过将写操作分配给一个单独的线程来解决它。

答案 5 :(得分:0)

this gist查看asynctask.m

asynctask.m允许处理超过8k的输入数据。