3

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

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

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

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

6 回答 6

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;
}];

这都是异步的(你应该异步执行),所以你的方法应该有一个 ^completion 块。

于 2013-04-29T08:50:26.237 回答
4

NSFileHandle availableData 似乎无限阻塞:

我制作了一个测试程序,我用另一个程序的 NSTask 调用它。我将 NSFileHandle 分配给标准输入并从管道中读取数据。测试程序使用 NSLog 函数在标准输出中充斥大量文本。似乎没有办法解决它,无论我使用 NSFileHandle 中的什么 API,迟早可用数据块,然后应用程序将无限挂起并且什么都不做。它实际上停止到数据读取语句,无论它是放在 while 中还是在其中。我也尝试过逐个读取字节,但也无济于事:

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

data = [file availableData]; // blocks infinitely

这会持续一段时间,直到它也冻结。似乎我注意到 NSFileHandle API 并不能真正与输出大量数据的 shell 命令一起使用,所以我必须改用 Posix API 来解决这个问题。

从 Stack Overflow 或 Internet 上的其他站点找到的如何使用此 API 部分读取数据的每个示例,无论是同步还是异步,似乎都阻止了最后一次 fileAvailableData 读取。

于 2012-08-24T11:12:06.473 回答
1

一个简单而痛苦的事实是,将大量数据写入子进程然后从其中读取大量数据并不是您可以在不阻塞 UI 的情况下在单个函数或方法中完成的事情。

解决方案同样简单,而且肯定是一个令人痛苦前景:使导出异步。尽可能写入数据,尽可能读取数据。这样,您不仅不会阻塞 UI,而且还能够为非常长的导出更新进度指示器,并并行执行多个导出(例如,从单独的文档)。

这是可行的,但 UI 收益很大,其结果是内部和外部的设计更简洁。

于 2009-09-09T11:00:54.790 回答
0

需要明确的是,这不仅很慢,而且在您使用调试器闯入之前实际上是冻结的?是不是你的子流程有问题?

人们会期望NSFileHandle处理你扔给它的任何数据,但也许你可以将你的数据分成更小的块-subdataWithRange:,看看有什么影响。您还可以获取fileDescriptor并使用 POSIX API(fdopen、fwrite 等)来写入流。POSIX API 将提供更大的灵活性,如果这确实是您所需要的。

于 2009-09-08T22:42:55.383 回答
0

我相信发生的事情是我遇到了一个死锁,这是由于[writeHandle writeData:[input dataUsingEncoding:NSUTF8StringEncoding]];行填充过度填充其缓冲区并导致应用程序挂起直到它(从不)清空。

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

于 2009-09-15T20:21:52.550 回答
0

asynctask.m这个要点看一下。

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

于 2013-01-18T14:44:44.367 回答