如何防止命令行工具在异步操作完成之前退出

问题描述 投票:0回答:9

在 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 但没有快速的例子

xcode swift2 xcode7-beta4
9个回答
44
投票

RunLoop.main.run()
添加到文件末尾是一种选择。有关使用信号量的另一种方法的更多信息此处


35
投票

我意识到这是一个老问题,但这是我最终的解决方案。使用DispatchGroup

let dispatchGroup = DispatchGroup()

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

dispatchGroup.notify(queue: DispatchQueue.main) {
    exit(EXIT_SUCCESS)
}
dispatchMain()

17
投票

您可以在 main 结束时调用

dispatchMain()
。它运行 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()

13
投票

不要依赖时间..你应该尝试这个

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()

7
投票

斯威夫特 4:

RunLoop.main.run()

在文件末尾


6
投票

如果您需要不是需要“生产级别”代码,但需要一些快速实验或试用一段代码,您可以这样做:

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


请注意,您不应依赖架构中的固定执行时间。


1
投票

现在,我们将使用 Swift 并发 和简单的

await
async
任务。例如,

do {
    guard let url = URL(string: "https://stackoverflow.com") else {
        throw URLError(.badURL)
    }

    let (data, _) = try await URLSession.shared.data(from: url)
    if let string = String(data: data, encoding: .utf8) {
        print(string)
    } else {
        print("Unable to generate string representation")
    }
} catch {
    print(error)
}

与传统的完成处理程序模式不同,如果您

await
async
任务,命令行应用程序将不会在异步任务完成之前终止。

有关更多信息,请参阅 WWDC 2021 视频认识 async/await 或该页面上列出的任何“相关视频”。


0
投票
// Step 1: Add isDone global flag

var isDone = false
// Step 2: Set isDone to true in callback

request.GET(...) {
    ...
    isDone = true
}

// Step 3: Add waiting block at the end of code

while(!isDone) {
    // run your code for 0.1 second
    RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.1))
}

0
投票

除了现在Rob的答案(Swift 5.5+)之外,还有一种创建异步命令行界面的便捷方法

@main
struct AsyncCLI { // the name is arbitrary 
    static func main() async throws {
        // your code goes here
    }
}

重要:

  • 随附文件不得命名为
    main.swift
© www.soinside.com 2019 - 2024. All rights reserved.