Метод readInBackgroundAndNotify не обновляется до завершения NSTask - PullRequest
3 голосов
/ 24 ноября 2011

Я пытаюсь запустить NSTask в фоновом потоке и отобразить его вывод в NSTextview, который находится на NSPanel, присоединенном к моему окну (Панель настроек), с использованием readInBackgroundAndNotify. Не похоже, что я получаю уведомления как метод, которыйдолжен быть вызван не.

У меня есть класс контроллера (PreferencePane.m) init класс (Inventory.m), который отвечает за запуск NSTask

- (IBAction)updateInventoryButtonPressed:(id)sender
{
    inventory = [[Inventory alloc] init];
....

Затем я отправляюэто NSNotification для запуска фона (из PreferencePane.m):

....
[[NSNotificationCenter defaultCenter]
 postNotificationName:NotificationInventoryRequested
 object:self];
}

Этот класс (Inventory.m) является наблюдателем этой константы (NotificationInventoryRequested) в переопределении инициализации

 - (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 для помощи при повторных запускахздравомыслие обрабатывается с помощью инвентаря. Запрашивается с использованием инвентаря ivarIsRunning

    -(void)runInventoryTask
  {
    inventoryIsRunning = YES;
 ....

Я дважды проверяю свою задачу и настраиваюdInBackgroundAndNotify, добавив себя в качестве наблюдателя.

 ....
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 в консоли).Я увидел этот поток и подумал, может быть, это был мой NSPanel, блокирующий цикл выполнения, поэтому я установил его как немодальный.Я также помню, как читал о том, что NSNotification не является синхронным, поэтому я подумал, возможно, потому что метод, который я вызываю с помощью NSNotification, чтобы проверить, я просто сделал это очень быстро:

- (IBAction)updateInventoryButtonPressed:(id)sender
{

inventory = [[Inventory alloc] init];

/*[[NSNotificationCenter defaultCenter]
 postNotificationName:NotificationInventoryRequested
 object:self];*/
[inventory inventoryRequested:self];
[self showPanel:sender];

Очевидно, что self там недопустимо,но это показало мне, что даже прямой вызов этого метода, похоже, не помог (таким образом, заставляя меня думать, что речь идет не о «блокировке» 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,поэтому у меня проблема с управлением потоком.

Ответы [ 3 ]

6 голосов
/ 25 ноября 2011

Я смог заставить это работать, добавив следующее

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

Это остановило уведомление только от отправки мне буфера (все мои выходные сразу в моем случае).

2 голосов
/ 10 февраля 2012

В соответствии с описанным поведением блокировки и подходом к решению NSUnbufferedIO это, кажется, стандартная проблема буферизации потока ввода / вывода (которая время от времени также поднимает свою уродливую голову в сценариях оболочки).

Для довольно подробной статьи по этой сложной проблеме см .: буферизация в стандартных потоках .

Один из способов избежать стандартной буферизации потока ввода-вывода - включить режим линейной буферизации на частиСам инструмент командной строки.Примеры:

  • tcpdump -l
  • grep --line-buffered
  • sed -l

Режим вывода с линейной буферизацией также часто может бытьвключается запуском инструмента командной строки в псевдотерминале (pty) с помощью команды script.

/usr/bin/script -q /dev/null /usr/bin/local/inventory --force | ...

В дополнение к образцу кода Moriarity от Apple, имеется еще пример кода NSTaskэто может стоить посмотреть, включая AMShellWrapper, asynctask.m и PseudoTTY.app (перечислено в http://cocoadev.com/index.pl?NSTask).

1 голос
/ 24 ноября 2011
[[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];

Вы блокируете свой пользовательский интерфейс, пока задача не будет выполнена. waitUntilExit следует считать вредным, по крайней мере по этой причине и, вероятно, по другим.

waitUntilExit использует цикл выполнения, поэтому я немного удивлен, если у окна нет возможности рисовать, пока оно ждет, но его все равно стоит изменить, и то и другое в качестве потенциального решения (если это действительно проблема ) и потому, что блокирование интерфейса само по себе плохо.

Рассмотрите возможность наблюдения за NSTaskDidTerminateNotification (который вы должны настроить перед запуском). Вы можете опубликовать собственное уведомление при получении этого уведомления.

...