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完成,所以我有一个流量控制问题。

+0

无耻的插件时间!我写了一个完整的替代(大部分)NSTask,以免再次处理所有'readInBackgroundAndNotify'文件夹:https://bitbucket.org/boredzo/prhtask/ –

+1

+1因为你的问题非常详细,它帮助我解决完全不相关的问题。 – Klox

我能够加入到得到这个工作,下面

NSDictionary *defaultEnvironment = [[NSProcessInfo processInfo] environment]; 
NSMutableDictionary *environment = [[NSMutableDictionary alloc] initWithDictionary:defaultEnvironment]; 
[environment setObject:@"YES" forKey:@"NSUnbufferedIO"]; 
[task setEnvironment:environment]; 

这停止该通知仅向我发送缓冲区(所有我在我的情况下,一次输出)。

+0

Brillant !!!!非常感谢 – Bgi

+0

我使用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(应在启动前设置)。当通知到达时,您可以发布自己的通知。

+0

对日志进行更仔细的检查,它看起来像被调用,并且文本最终在我的文本视图中结束,它只是在任务完成之前(这需要一段时间),因此我的困惑没有做。我实际上是按照你最初描述的来编码的,但是从苹果公司的Moriority样本中借用了一些代码。试图找出现在的阻塞。任何人对此事的想法,将不胜感激 – acidprime

+0

@acidprime:我编辑了我的答案。 –

+0

感谢您的帮助,我不相信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示例代码,可能是值得一看包括AMShellWrapperasynctask.mPseudoTTY.app(在http://cocoadev.com/index.pl?NSTask列出)​​。