2

У меня есть следующая ситуация, когда я создаю очередь отправки GCD, и в ней я планирую NSStream к текущему NSRunLoop, как это требуется в его спецификации, чтобы он выдавал события делегата, а затем я создаю цикл выполнения для этого потока используя [[NSRunLoop currentRunLoop run].Как справиться с проблемами параллелизма, возникающими при планировании цикла запуска NSStream с использованием GCD?

Это создает три возможных сценария:

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

  2. Создайте параллельную очередь, в которой сообщения могут быть записаны в поток свободно, так как блоки, отправленные в очередь, будут выполняться одновременно с блоком, который запускает цикл цикла. Однако, хотя желательно, чтобы сообщения для записи и цикл выполнения выполнялись одновременно, конечно, нежелательно иметь блоки в очереди, одновременно предпринимая попытку записи одновременно с потоком.

  3. Создайте две очереди - один отвечает за поддержание цикла запуска и получение обратных вызовов с обратным потоком, а другой - для отправки асинхронных сообщений записи в поток. Это, казалось бы идеальным, однако, кажется, что документация NSStream конкретно говорится, что не следует пытаться читать/писать в поток вне нити его планируется в.

не Учитывая эти сценарии ни один из которых являются идеальными, как решить эти проблемы?

ответ

3

Как вы отметили в документах, когда у вас есть API на основе цикла выполнения, например NSStream, общее ожидание заключается в том, что все взаимодействие с этим объектом будет происходить в потоке, которому принадлежит цикл выполнения, на котором он запланирован. Я не уверен, что есть любое преимущество в смешении этих двух идиом (GCD и run loop), когда дело доходит до работы с NSStream.

Кроме основной очереди, НОД не имеет понятия потока сродства, так что если цикл выполнения не планировать NSStream на случается основной цикл нити запустить, нет хорошего способа использовать dispatch_async планировать блоки для выполнения по этой теме.

С риском указания очевидного следует, вероятно, просто использовать стандартные методы для методов планирования для других потоков. -performSelector:onThread:withObject:waitUntilDone:modes: является наиболее очевидным. Если ваше замешательство заключается в том, что вы хотите работать с блоками, это помогает знать, что блоки, выделенные кучей, можно рассматривать как объекты Objective-C и реализовать селектор -invoke так же, как NSInvocation. Тривиальный пример отношения к вашему вопросу может выглядеть следующим образом:

@interface AppDelegate() 
{ 
    NSThread* bgthread; 
} 
@end 

@implementation AppDelegate 

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification 
{ 
    // Basic loop to get the background thread to run until you call -cancel on it 
    dispatch_block_t threadMain = [^{ 
     NSThread* thread = [NSThread currentThread]; 
     NSParameterAssert(![thread isMainThread]); 

     NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop]; 
     NSPort* port = [NSPort port]; 

     // If we dont register a mach port with the run loop, it will just exit immediately 
     [currentRunLoop addPort: port forMode: NSRunLoopCommonModes]; 

     // Loop until the thread is cancelled. 
     while (!thread.cancelled) 
     { 
      [currentRunLoop runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]]; 
     } 

     [currentRunLoop removePort: port forMode: NSRunLoopCommonModes]; 

     [port invalidate]; 
     port = nil; 
    } copy]; 

    // Start the thread 
    bgthread = [[NSThread alloc] initWithTarget: threadMain selector: @selector(invoke) object: nil]; 
    [bgthread start]; 

    // Fetch the runloop, so you can schedule an NSStream on it... 
    __block NSRunLoop* runloopForStream = nil; 
    dispatch_block_t getrunloop = [^{ 
     runloopForStream = [NSRunLoop currentRunLoop]; 
    } copy]; 

    // Dispatch synchronously, so that runloopForStream is populated before we continue... 
    [getrunloop performSelector: @selector(invoke) onThread: bgthread withObject: nil waitUntilDone: YES]; 

    // Schedule your stream, etc. 
    NSOutputStream* mystream = ...; // Your code here... 
    [mystream scheduleInRunLoop: runloopForStream forMode: NSDefaultRunLoopMode]; 

    // Then later, when you want to write some data... 
    NSData* dataToWrite = [NSMutableData dataWithLength: 100]; 
    dispatch_block_t doWrite = [^{ 
     [mystream write: dataToWrite.bytes maxLength: dataToWrite.length]; 
    } copy]; 

    // Dispatch asynchronously to thread 
    [doWrite performSelector: @selector(invoke) onThread: bgthread withObject: nil waitUntilDone: NO]; 
} 
@end 

Обратите внимание, что -copy блоков необходимо, чтобы их скопировать в кучу, в противном случае они будут высвобождены, когда метод объявляя выходит из области видимости ,

1

Late к партии, но вместо того, чтобы использовать runloops вы можете установить нужную очередь отправки для потоков непосредственно с помощью

void CFReadStreamSetDispatchQueue(CFReadStreamRef stream, dispatch_queue_t q); 
void CFWriteStreamSetDispatchQueue(CFWriteStreamRef stream, dispatch_queue_t q); 

Где CFReadStreamRef может занять мостовом NSInputStream и CFWriteStreamRef мостиковым NSOutputStream. Таким образом, вам не нужно планировать или отключать runloops вообще, и ваши потоки будут работать в фоновом режиме.

Отрывок из этого Apple sample code:

CFReadStreamSetDispatchQueue((__bridge CFReadStreamRef) self.inputStream, self.queue); 
CFWriteStreamSetDispatchQueue((__bridge CFWriteStreamRef) self.outputStream, self.queue); 

В Swift, вы можете просто напрямую вызывать функции:

CFReadStreamSetDispatchQueue(inputStream, streamQueue) 
CFWriteStreamSetDispatchQueue(outputStream, streamQueue) 
Смежные вопросы