Swift 2 - iOS - отправить обратно в исходный поток

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

каркасный код как таковой

let Session = NSURLSession.sharedSession()
let TheStack = [Structure]()
//This gets called asynchronously, e.g. in threads 3,4,5,6,7
func AddToStack(The Response) -> Void { 
   TheStack.insertAt(Structure(The Response), atIndex: 0))
   if output.hasSpaceAvailable == true {
      // This causes the stream event to be fired on mutliple threads
      // This is what I want to call back into the original thread, e.g. in thread 2
      self.stream(self.output, handleEvent: NSStreamEvent.hasSpaceAvailable) 
   }
}

// This is in the main loop, e.g. thread 2
func stream(aStream: NSStream, handleEvent: NSStreamEvent) {

   switch(NSStreamEvent) {

      case NSStreamEvent.OpenCompleted:
          // Do some open stuff
      case NSStreamEvent.HasBytesAvailable:
          Session.dataTaskWithRequest(requestFromInput, completionHandler: AddToStack)
      case NSStreamEvent.HasSpaceAvailable:
          // Do stuff with the output
      case NSStreamEvent.CloseCompleted:
          // Close the stuff
   }
}

Проблема в том, что поток, который вызывает dataTaskWithRequest, находится в потоке, скажем, 3. Обработчик завершения срабатывает во многих разных потоках и заставляет case NSStreamEvent.HasSpaceAvailable: выполняться в потоке 3, а также во всех потоках, в которых они существовали.

Мой вопрос: как мне сделать так, чтобы self.stream(self.output, handleEvent: NSStreamEvent.hasSpaceAvailable) вызывался в потоке 3 или каким-либо исходным потоком, чтобы предотвратить это спотыкание друг о друга на этапе вывода.

Заранее спасибо!

ПРИМЕЧАНИЕ. Поток, содержащий обработку ввода-вывода, был создан с помощью NSThread.detachNewThreadSelector.


person Ajwhiteway    schedule 15.01.2016    source источник
comment
Как насчет создания собственной очереди (очередей) и передачи той, которая должна использоваться вашим писателем? (Я говорю очередь, потому что вы пометили gcd.)   -  person Phillip Mills    schedule 15.01.2016
comment
Я подумал об этом примерно за 5 минут до того, как мне нужно было уйти с работы. Создайте поток/очередь и направьте туда все выходные данные. Таким образом, даже если есть несколько вызывающих потоков, они не все пытаются выполниться одновременно. У меня выходной до вторника, так что, если я пойду по этому пути, я дам вам знать, как все пройдет.   -  person Ajwhiteway    schedule 16.01.2016
comment
У меня уже есть NSThread, созданный NSThread.detachNewThreadSelector. Это поток, в который мне нужно перезвонить. Я боюсь, что диспетчеризация может на самом деле усугубить проблему (потоки закрываются до того, как все будет завершено)   -  person Ajwhiteway    schedule 20.01.2016
comment
Если вы используете NSThread.detachNewThreadSelector в коде, написанном после iOS 4, вы делаете что-то не так. Если вы используете его в более старом коде (или в коде до 10.6), вы все еще, вероятно, делаете что-то не так. Тот факт, что вы даже обсуждаете конкретный поток, в котором что-то работает в коде ObjC, означает, что вы неправильно подходите к проблеме (и поэтому у вас возникают эти проблемы). Начните с прочтения документа Apple «Миграция от потоков» (для разработчиков). .apple.com/library/ios/documentation/General/).   -  person Rob Napier    schedule 20.01.2016
comment
Если вам нужен хороший пример правильного использования потоков с GCD (через CFStream, но это очень похоже на NSStream), см. код GCD в CocoaAsyncSocket. github.com/robbiehanson/CocoaAsyncSocket/tree/master/Source/GCD. Это лучшая известная мне библиотека для управления сетевыми сокетами.   -  person Rob Napier    schedule 20.01.2016
comment
Спасибо, посмотрю. Я смотрел на использование PerformSelector, но, похоже, это не то, что мне нужно. В идеале я хотел бы сделать это, не переписывая свой небольшой «сервер» на 1000 строк.   -  person Ajwhiteway    schedule 20.01.2016
comment
Обдумывая это с моим коллегой, я думаю, что в конечном итоге перепишу его, чтобы он работал через Grand Central Dispatch, а не через NSThread. Спасибо! Это мой первый проект на iOS/Swift, поэтому в реализации должны были быть какие-то ошибки. Можете ли вы порекомендовать какие-либо другие ссылки для просмотра перед переписыванием, чтобы защититься от других ловушек?   -  person Ajwhiteway    schedule 20.01.2016
comment
Я бы использовал семейство performSelector вместо GCD, когда это возможно, поскольку оно скрывает и обрабатывает waitUntilDone для вас. Вот обсуждение, которое может оказаться полезным: stackoverflow. com/a/34540787/218152.   -  person SwiftArchitect    schedule 21.01.2016
comment
Спасибо, я еще раз взгляну на PerformSelector!   -  person Ajwhiteway    schedule 21.01.2016
comment
Если вы пытаетесь синхронизировать несколько асинхронных событий, вы можете использовать диспетчерские семафоры.   -  person Michael    schedule 22.01.2016


Ответы (2)


Хорошо, для любопытного зрителя я, с помощью комментариев к вопросу, понял, как сделать то, что я изначально задал в вопросе (независимо от того, будет ли это в конечном итоге переписано для использования GCD, это другой вопрос)

Решение (с немного увеличенным объемом кода) состоит в том, чтобы использовать PerformSelector с конкретным потоком.

final class ArbitraryConnection {

internal var streamThread: NSThread

let Session = NSURLSession.sharedSession()
let TheStack = [Structure]()
//This gets called asynchronously, e.g. in threads 3,4,5,6,7
func AddToStack(The Response) -> Void { 
   TheStack.insertAt(Structure(The Response), atIndex: 0))
   if output.hasSpaceAvailable == true {
      // This causes the stream event to be fired on multiple threads
      // This is what I want to call back into the original thread, e.g. in thread 2

      // Old way
      self.stream(self.output, handleEvent: NSStreamEvent.hasSpaceAvailable)
      // New way, that works
      if(streamThread != nil) {
          self.performSelector(Selector("startoutput"), onThread: streamThread!, withObject: nil, waitUntilDone: false)
      }
   }
}

func open -> Bool {
    // Some stuff
    streamThread = NSThread.currentThread()
}


final internal func startoutput -> Void {
   if(output.hasSpaceAvailable && outputIdle) {
        self.stream(self.output, handleEvent: NSStreamEvent.HasSpaceAvailable)
   }
}
// This is in the main loop, e.g. thread 2
func stream(aStream: NSStream, handleEvent: NSStreamEvent) {

   switch(NSStreamEvent) {

      case NSStreamEvent.OpenCompleted:
          // Do some open stuff
      case NSStreamEvent.HasBytesAvailable:
          Session.dataTaskWithRequest(requestFromInput, completionHandler: AddToStack)
      case NSStreamEvent.HasSpaceAvailable:
          // Do stuff with the output
      case NSStreamEvent.CloseCompleted:
          // Close the stuff
   }
}
}

Поэтому используйте PerformSelector для объекта с селектором и используйте onThread, чтобы сообщить ему, к какому потоку перейти. Я проверяю как перед выполнением селектора, так и перед выполнением вызова, чтобы убедиться, что на выходе есть свободное место (убедитесь, что я не спотыкаюсь о себя)

person Ajwhiteway    schedule 22.01.2016
comment
Я бы придерживался performSelector, так как он оборачивает GCD и решает головоломки, связанные с отправкой из одной или разных очередей. Интерес представляет waitUntilDone: false, который я обычно предпочитаю true, чтобы обеспечить правильную последовательность операций. - person SwiftArchitect; 26.01.2016
comment
Да, я завершил проект для тестирования, пока performSelector все еще используется. Из-за того, как они порождаются, обычно существует только один поток для каждого потока (с этим справляется dataTaskWithRequest). Таким образом, делая его false, я разрешаю очистку потока после добавления данных в очередь для буфера. Все еще нужно пройти некоторое тестирование, чтобы убедиться, что все держится вместе при включении в более крупный проект. - person Ajwhiteway; 26.01.2016
comment
Вы, кажется, полностью контролируете себя! Мой опыт работы с performSelector и performBlock заключается в том, что я обычно сожалею об экономии миллисекунд здесь и там, и должен вернуться к waitUntilDone и performBlockAndWait для надежности и предсказуемости. - person SwiftArchitect; 26.01.2016
comment
Это хорошее замечание для любого читателя. waitUntilDone = false будет исключением, а не стандартом. - person Ajwhiteway; 26.01.2016

Это не позволит мне прокомментировать ветку выше (это то, что я получаю за то, что скрываюсь), но одна вещь, о которой следует знать, это то, что ваш текущий код может заблокировать ваш пользовательский интерфейс, если вы используете waitUntilDone или performBlockAndWait.

Если вы пойдете по этому пути, вам нужно быть абсолютно уверенным, что вы не вызываете это из mainThread или не имеете резервного случая, который порождает новый поток.

person DerailedLogic    schedule 27.01.2016
comment
Это хороший момент, который я явно не обрабатывал в своем коде, в моем случае обработчик завершения dataTaskWithRequest никогда не должен заканчиваться внутри основного потока графического интерфейса. В любом случае хороший момент. Поскольку вы были единственным человеком, кроме меня, кто ответил на вопрос, наслаждайтесь наградой! - person Ajwhiteway; 27.01.2016