readInBackgroundAndNotify方法没有更新,直到NSTask完成[已更新]
所以我想在后台线程中运行一个NSTask并使用readInBackgroundAndNotify将它的输出显示在NSPanel窗口中的NSPanel上(偏好窗格) 它确实似乎没有收到通知,因为应该调用的方法不是。readInBackgroundAndNotify方法没有更新,直到NSTask完成[已更新]
我有控制器类(PreferencePane.m)初始化类(Inventory.m),其负责运行NSTask
- (IBAction)updateInventoryButtonPressed:(id)sender
{
inventory = [[Inventory alloc] init];
....
然后我发送一个NSNotification启动后台(从PreferencePane .M):
....
[[NSNotificationCenter defaultCenter]
postNotificationName:NotificationInventoryRequested
object:self];
}
这个类(Inventory.m)是此常数(NotificationInventoryRequested)观察者在其init倍率
- (id)init
{
[super init];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(inventoryRequested:)
name:NotificationInventoryRequested
object:nil];
return self;
}
这将运行inventoryRequested方法(的Inventory.m)
-(void)inventoryRequested:(NSNotification*)aNotification
{
if (inventoryIsRunning) {
NSLog(@"Inventory is already running, ignoring request");
}
else {
NSLog(@"Starting Inventory in background...");
[NSThread detachNewThreadSelector:@selector(runInventoryTask)
toTarget:self
withObject:nil];
}
}
这将运行我的NSTask方法,我从例子
设置一个BOOL,以帮助重构重复几次运行理智的处理通过使用inventoryRequested伊娃inventoryIsRunning
-(void)runInventoryTask
{
inventoryIsRunning = YES;
....
我仔细检查我的任务和设置readInBackgroundAndNotify加入我自己作为一个观察者。
....
if (task) {
NSLog(@"Found existing task...releasing");
[task release];
}
task = [[NSTask alloc] init];
NSLog(@"Setting up pipe");
[task setStandardOutput: [NSPipe pipe]];
[task setStandardError: [task standardOutput]];
// Setup our arguments
[task setLaunchPath:@"/usr/bin/local/inventory"];
[task setArguments:[NSArray arrayWithObjects:@"--force",
nil]];
//Said to help with Xcode now showing logs
//[task setStandardInput:[NSPipe pipe]];
[self performSelectorOnMainThread:@selector(addLogText:)
withObject:@"Launching Task..."
waitUntilDone:false];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(readPipe:)
name: NSFileHandleReadCompletionNotification
object: [[task standardOutput] fileHandleForReading]];
[[[task standardOutput] fileHandleForReading] readInBackgroundAndNotify];
[task launch];
[task waitUntilExit];
// Let Any Observers know we are finished with Inventory
[[NSNotificationCenter defaultCenter]
postNotificationName:NotificationInventoryComplete
object:self];
inventoryIsRunning = NO;
}
这一切似乎运行良好。但这种方法永远不会被调用(即我没有看到窗口更新或控制台中的NSLog):
-(void)readPipe:(NSNotification *)notification
{
NSData *data;
NSString *text;
NSLog(@"Read Pipe was called");
data = [[notification userInfo]
objectForKey:NSFileHandleNotificationDataItem];
if ([data length]){
text = [[NSString alloc] initWithData:data
encoding:NSASCIIStringEncoding];
// Update the text in our text view
[self performSelectorOnMainThread:@selector(addLogText:)
withObject:text
waitUntilDone:false];
NSLog(@"%@",text);
[text release];
}
[[notification object] readInBackgroundAndNotify];
}
这一切似乎运行良好。但是这个方法永远不会被调用(即,我没有在控制台中看到窗口更新或NSLog)。我看到这thread,并认为它可能是我的NSPanel阻止运行循环,所以我将它设置为非模态。我还记得阅读NSNotification的不是同步的,所以我想也许是因为该方法我正被一个NSNotification调用,来测试我只是做了这个真正的快:
- (IBAction)updateInventoryButtonPressed:(id)sender
{
inventory = [[Inventory alloc] init];
/*[[NSNotificationCenter defaultCenter]
postNotificationName:NotificationInventoryRequested
object:self];*/
[inventory inventoryRequested:self];
[self showPanel:sender];
显然,自我是无效的存在,但它告诉我,即使直接调用此方法似乎没有帮助(因此使我认为这不是关于NSNotification“阻止”。)
任何思考我缺少什么,我检查了removeObserver在我的任何地方代码(我知道我需要将它添加到dealloc,并可能在readPipe中:当命令运行完成时)。如果这里有帮助的话,那就是需要工作的小NSTextview包装器,因为我现在不会在字符串中执行\ n。
Inventory.h
//NSTextview
IBOutlet NSTextView * inventoryTextView;
Inventory.m
-(void)addLogText:(NSString *)text
{
NSRange myRange = NSMakeRange([[inventoryTextView textStorage] length], 0);
[[inventoryTextView textStorage] replaceCharactersInRange:myRange withString:text];
}
任何帮助,这未免太理解,因为它我的下一个绊脚石。
已更新: 看起来像这样readData方法正在被调用,但它没有更新我的Textview,直到NSTask完成,所以我有一个流量控制问题。
我能够加入到得到这个工作,下面
NSDictionary *defaultEnvironment = [[NSProcessInfo processInfo] environment];
NSMutableDictionary *environment = [[NSMutableDictionary alloc] initWithDictionary:defaultEnvironment];
[environment setObject:@"YES" forKey:@"NSUnbufferedIO"];
[task setEnvironment:environment];
这停止该通知仅向我发送缓冲区(所有我在我的情况下,一次输出)。
Brillant !!!!非常感谢 – Bgi
我使用python作为一项任务,除了这样做之外,还必须为python指定-u选项 –
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(readPipe:) name: NSFileHandleReadCompletionNotification object: [[task standardOutput] fileHandleForReading]]; [[[task standardOutput] fileHandleForReading] readInBackgroundAndNotify];
你假定管会两次返回相同的NSFileHandle对象。
如果没有,那么发布通知的对象和您观察的对象将不会是同一个对象。这可以解释为什么你没有看到通知,所以我不会指望这一点。
尝试单独获取NSFileHandle并将其分配给一个变量,然后在addObserver:selector:name:object:
和readInBackgroundAndNotify
消息表达式中使用该变量。
[task waitUntilExit];
,直到任务完成后你挡住你的UI。 waitUntilExit
应该被认为是有害的,至少为此,也可能是其他。
waitUntilExit
使用运行循环,因此如果窗口在等待时没有机会绘制,我感到非常惊讶,但无论如何,这是值得改变的,这两者都是潜在的修复(如果确实是这个问题)因为阻止用户界面本身就不好。
请考虑观察NSTaskDidTerminateNotification
(应在启动前设置)。当通知到达时,您可以发布自己的通知。
对日志进行更仔细的检查,它看起来像被调用,并且文本最终在我的文本视图中结束,它只是在任务完成之前(这需要一段时间),因此我的困惑没有做。我实际上是按照你最初描述的来编码的,但是从苹果公司的Moriority样本中借用了一些代码。试图找出现在的阻塞。任何人对此事的想法,将不胜感激 – acidprime
@acidprime:我编辑了我的答案。 –
感谢您的帮助,我不相信waitUntilExit是一个问题,因为我的NStask在自己的线程中运行,但是我实现了NSTaskDidTerminateNotification,因为它看起来像是一个更好的解决方案。 – acidprime
根据描述的阻塞行为和NSUnbufferedIO
解决方案的方法,它似乎是一个标准的I/O流缓冲问题(它不时在shell脚本中也不断抬头)。
关于这个棘手问题的相当详细的文章请参阅:buffering in standard streams。
避免标准I/O流缓冲的方法是在部分命令行工具本身上启用行缓冲模式。实例:
tcpdump -l
grep --line-buffered
sed -l
行缓冲的输出模式也常常通过使用script
运行在一个伪终端(PTY)的命令行工具来启用命令。
/usr/bin/script -q /dev/null /usr/bin/local/inventory --force | ...
除了来自苹果Moriarity
示例代码有进一步NSTask
示例代码,可能是值得一看包括AMShellWrapper
,asynctask.m
和PseudoTTY.app
(在http://cocoadev.com/index.pl?NSTask列出)。
无耻的插件时间!我写了一个完整的替代(大部分)NSTask,以免再次处理所有'readInBackgroundAndNotify'文件夹:https://bitbucket.org/boredzo/prhtask/ –
+1因为你的问题非常详细,它帮助我解决完全不相关的问题。 – Klox