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

发布于 2024-08-04 08:48:07 字数 1498 浏览 6 评论 0原文

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

我在 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;
}

I've got a bit of code that handles exporting data from my application. It takes in an NSString full of XML and runs it through a PHP script to generate HTMl, RTF, etc. It works well unless a user has a large list. This is apparently due to it overrunning the 8k or so buffer of NSPipe.

I worked around it (I think) in the readPipe and readHandle, but I'm not sure how to handle it in the writeHandle/writePipe. The application will beachball at [writeHandle writeData:[in... unless I break on it in gdb, wait a few seconds and and then continue.

Any help on how I can workaround this in my code?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

记忆で 2024-08-11 08:48:07

从 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 块。

There's a new API since 10.7, so you can avoid using 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];
}];

Probably you want to repeat the same above for task.standardError.

IMPORTANT:

When your task terminates, you have to set readabilityHandler block to nil; otherwise, you'll encounter high CPU usage, as the reading will never stop.

[task setTerminationHandler:^(NSTask *task) {

    // do your stuff on completion

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

This is all asynchronous (and you should do it async), so your method should have a ^completion block.

我纯我任性 2024-08-11 08:48:07

NSFileHandle availableData 似乎无限阻塞:

我制作了一个测试程序,从另一个程序使用 NSTask 调用它。我将 NSFileHandle 分配给 stdin 并从管道读取数据。测试程序使用 NSLog 函数向标准输出发送大量文本。似乎没有办法解决这个问题,无论我在 NSFileHandle 中使用什么 API,迟早 availableData 都会阻塞,然后应用程序将无限挂起并且不执行任何操作。它实际上停止到数据读取语句,无论它是放在 while 中还是里面。我也尝试过读取字节,一个接一个,没有帮助
要么:

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

data = [file availableData]; // blocks infinitely

这会工作一段时间,直到它也冻结。似乎我已经注意到 NSFileHandle API 并不真正适用于输出大量数据的 shell 命令,因此我必须使用 Posix API 来解决这个问题。

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

NSFileHandle availableData seems to block infinitely:

I made a test program that I call with NSTask from another program. I assign the NSFileHandle to stdin and read the data from pipe. The test program floods the stdout with lots of text using NSLog function. There seems to be no way around it, no matter what API in the NSFileHandle I use, sooner or later the availableData blocks and then the app will hang infinitely and will do nothing. It actually stops to the data read statement, no matter if it is placed in the while or inside it. I tried also reading bytes, one by one, does not help
either:

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

data = [file availableData]; // blocks infinitely

This works for a while until it also freezes. Seems that I have noticed that the NSFileHandle API does not really work with shell commands that output lots of data, so I have to work around this by using Posix API instead.

Every single example of how to read the data in parts with this API found from Stack Overflow or other sites in the Internet, either synchronously, or asynchronously, seems to block to last fileAvailableData read.

紫瑟鸿黎 2024-08-11 08:48:07

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

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

这是可行的,但用户界面的回报是巨大的,其结果是内部和外部的设计都更加简洁。

The simple, painful truth is that writing a lot of data to a subprocess and then reading a lot of data back from it is not something you can do in a single function or method without blocking the UI.

The solution is just as simple, and is certainly a painful-looking prospect: Make the export asynchronous. Write data as you can, and read data as you can. Not only are you then not blocking the UI, you also gain the ability to update a progress indicator for a really long export, and to do multiple exports in parallel (e.g., from separate documents).

It's work, but the UI payoffs are big, and the result is a cleaner design both internally and externally.

夜光 2024-08-11 08:48:07

需要明确的是,这不仅很慢,而且实际上会冻结,直到您使用调试器进行闯入?这不是你的子流程的问题吗?

人们会期望 NSFileHandle 能够处理您向其抛出的任何数据,但也许您可以使用 -subdataWithRange: 将数据分割成更小的块,看看会产生什么效果。您还可以获取 fileDescriptor 并使用 POSIX API(fdopen、fwrite 等)写入流。如果您确实需要的话,POSIX API 将提供更大的灵活性。

To be clear, this isn't just slow but it's actually freezing until you break in with a debugger? It's not a problem with your sub-process?

One would expect NSFileHandle to handle any data you throw at it, but perhaps you can split your data into smaller chunks using -subdataWithRange: to see what effect that has. You could also grab the fileDescriptor and use the POSIX APIs (fdopen, fwrite, etc.) to write to the stream. The POSIX APIs would provide more flexibility, if indeed that's what you need.

层林尽染 2024-08-11 08:48:07

我相信发生的事情是由于 [writeHandle writeData:[input dataUsingEncoding:NSUTF8StringEncoding]]; 行填充溢出其缓冲区并导致应用程序挂起,直到它(永远不会)清空。

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

I believe what was happening was I was running into a deadlock caused by the [writeHandle writeData:[input dataUsingEncoding:NSUTF8StringEncoding]]; line filling overfilling its buffer and causing the application to hang until it is (never) emptied.

I worked around it by dispatching the write operation off to a separate thread.

对你再特殊 2024-08-11 08:48:07

查看此要点中的asynctask.m

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

Have a look at asynctask.m from this gist.

asynctask.m allows to process more than 8k of input data.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文