Как предотвратить выход инструмента командной строки до завершения асинхронной операции

В инструменте командной строки swift 2 (main.swift) у меня есть следующее:

import Foundation
print("yay")

var request = HTTPTask()
request.GET("http://www.stackoverflow.com", parameters: nil, completionHandler: {(response: HTTPResponse) in
    if let err = response.error {
        print("error: \(err.localizedDescription)")
        return //also notify app of failure as needed
    }
    if let data = response.responseObject as? NSData {
        let str = NSString(data: data, encoding: NSUTF8StringEncoding)
        print("response: \(str)") //prints the HTML of the page
    }
})

Консоль показывает «yay», а затем завершает работу (программа завершилась с кодом выхода: 0), по-видимому, даже не дожидаясь завершения запроса. Как мне предотвратить это?

В коде используется swiftHTTP.

Думаю, мне может понадобиться NSRunLoop, но быстрого примера нет


person codecowboy    schedule 11.08.2015    source источник
comment
Должен ли он быть асинхронным?   -  person trojanfoe    schedule 11.08.2015
comment
@trojanfoe не обязательно, но меня интересует в любом случае, асинхронный или нет. Я решил попробовать использовать github.com/daltoniam/SwiftHTTP в качестве эксперимента.   -  person codecowboy    schedule 11.08.2015
comment
Возможный дубликат CFRunLoop в программе командной строки Swift (который имеет< /i> пример Swift для NSRunLoop).   -  person Martin R    schedule 11.08.2015
comment
Вы можете использовать readline(), если вы просто отлаживаете, он будет поддерживать выполнение цикла выполнения в ожидании ввода. Или вы можете использовать его, чтобы фактически заставить пользователя выйти по своему выбору, проверяя ввод на y или что-то в этом роде.   -  person Sajad Khan    schedule 01.08.2018


Ответы (6)


Я понимаю, что это старый вопрос, но вот решение, на котором я закончил. Используя DispatchGroup.

let dispatchGroup = DispatchGroup()

for someItem in items {
    dispatchGroup.enter()
    doSomeAsyncWork(item: someItem) {
        dispatchGroup.leave()
    }
}

dispatchGroup.notify(queue: DispatchQueue.main) {
    exit(EXIT_SUCCESS)
}
dispatchMain()
person skwashua    schedule 23.06.2018

Одним из вариантов является добавление RunLoop.main.run() в конец файла. Подробнее о другом подходе с использованием семафора здесь

person codecowboy    schedule 11.08.2015
comment
Стриж 4: RunLoop.main.run() - person e a; 21.01.2018

Вы можете вызвать dispatchMain() в конце main. Это запускает диспетчер основной очереди GCD и никогда не возвращается, поэтому это предотвратит выход из основного потока. Затем вам просто нужно явно вызвать exit(), чтобы выйти из приложения, когда вы будете готовы (иначе приложение командной строки зависнет).

import Foundation

let url = URL(string:"http://www.stackoverflow.com")!
let dataTask = URLSession.shared.dataTask(with:url) { (data, response, error) in
    // handle the network response
    print("data=\(data)")
    print("response=\(response)")
    print("error=\(error)")

    // explicitly exit the program after response is handled
    exit(EXIT_SUCCESS)
}
dataTask.resume()

// Run GCD main dispatcher, this function never returns, call exit() elsewhere to quit the program or it will hang
dispatchMain()
person Mike Vosseller    schedule 08.01.2017
comment
Это элегантное решение imo. Он имеет наименьшую когнитивную нагрузку. - person gprasant; 08.07.2017
comment
Может быть, за него проголосовали, потому что тот, кто проголосовал против, не смог найти dispatchMain(). Я думаю, что Майк имел в виду dispatch_main(). - person Jerry Krinock; 28.10.2017
comment
comment
Xcode 10.1, похоже, содержит ошибки при использовании этого и не позволяет вам остановить процесс, если вы не выйдете из Xcode (kill -9 останавливает его, но Xcode этого не понимает). RunLoop.main.run() работает однако - person Allison; 24.02.2019
comment
@gprasant, пожалуйста, объясните, почему displatchMain() является лучшим решением, чем DispatchSemaphore (значение: 0) - person Dblock247; 21.08.2019

Не зависьте от времени .. Вы должны попробовать это

let sema = DispatchSemaphore(value: 0)

let url = URL(string: "https://upload.wikimedia.org/wikipedia/commons/4/4d/Cat_November_2010-1a.jpg")!

let task = URLSession.shared.dataTask(with: url) { data, response, error in
  print("after image is downloaded")

  // signals the process to continue
  sema.signal()
}

task.resume()

// sets the process to wait
sema.wait()
person thiagoh    schedule 18.01.2017
comment
Статья здесь мне тоже пригодилась - person Kdawgwilk; 22.02.2017

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

SWIFT 3

//put at the end of your main file
RunLoop.main.run(until: Date(timeIntervalSinceNow: 15))  //will run your app for 15 seconds only

Дополнительная информация: https://stackoverflow.com/a/40870157/469614


Обратите внимание, что в вашей архитектуре не следует полагаться на фиксированное время выполнения.

person Vexy    schedule 29.11.2016
comment
Жестко закодированные временные интервалы для асинхронных задач никогда не бывают хорошей идеей. Интерфейс командной строки должен запускать цикл (без timeout) и явно останавливать его после выхода из блока завершения. Предполагается, что любой тайм-аут контролируется классом HTTPTask или URLSession. - person vadian; 29.11.2016
comment
Как описано в разделе ПРИМЕЧАНИЯ моего первоначального ответа;) - person Vexy; 29.11.2016
comment
может ли sleep(15) проделать тот же трюк? Я делаю так в CodeRunner - person Kent Liau; 13.12.2016
comment
Просто добавим к этому: чтобы он работал бесконечно, вы можете использовать Date.distantFuture. - person Kilian; 30.12.2016

Стриж 4: RunLoop.main.run()

В конце вашего файла

person Chris Karani    schedule 16.12.2018
comment
тогда он никогда не выйдет - person Woodstock; 15.08.2020