如何退出运行循环?

Posted

技术标签:

【中文标题】如何退出运行循环?【英文标题】:How to exit a runloop? 【发布时间】:2017-05-06 20:50:39 【问题描述】:

所以,我有一个 Swift 命令行程序:

import Foundation

print("start")

startAsyncNetworkingStuff()

RunLoop.current.run()

print("end")

代码编译没有错误。异步网络代码运行良好,获取其所有数据,打印结果,并最终调用其完成函数。

如何让完成函数跳出上述当前运行循环,以便打印最后一个“结束”?

添加:

将 RunLoop.current.run() 替换为以下内容:

print("start")

var shouldKeepRunning = true

startAsyncNetworkingStuff()

let runLoop = RunLoop.current
while (   shouldKeepRunning
       && runLoop.run(mode:   .defaultRunLoopMode,
                      before: .distantFuture ) ) 


print("end")

设置

shouldKeepRunning = false

在异步网络完成功能中仍然不会导致打印“结束”。 (这是通过将 shouldKeepRunning = false 语句与实际打印到控制台的 print 语句括起来来检查的)。缺少什么?

【问题讨论】:

run documentation 中,他们说“如果您希望运行循环终止,则不应使用此 [run] 方法。相反,请使用其他 run 方法之一并在循环中检查您自己的其他任意条件。”后面的例子在 Objective-C 中,但它说明了这个想法。 根据其他建议的运行方法查看问题的更新。 “最终”结果没有变化。 【参考方案1】:

对于命令行界面,使用此模式并将完成处理程序添加到您的 AsyncNetworkingStuff(感谢 Rob 对代码的改进):

print("start")

let runLoop = CFRunLoopGetCurrent()
startAsyncNetworkingStuff()  result in 
   CFRunLoopStop(runLoop)


CFRunLoopRun()
print("end")
exit(EXIT_SUCCESS)

请不要使用丑陋的while 循环。


更新:

在带有 async/await 的 Swift 5.5+ 中,它变得更加舒适。不再需要维护运行循环。

将文件main.swift 重命名为其他名称,并像在普通应用程序中一样使用@main 属性。

@main
struct CLI 

    static func main() async throws 
        let result = await startAsyncNetworkingStuff()
        // do something with result 
    

struct的名字是任意的,静态函数main是强制的,是入口点。

【讨论】:

丑吗?但苹果官方文档中推荐:developer.apple.com/reference/foundation/runloop/1412430-run 丑陋的。我猜(Objective-C)文档在引入块之前已经编写(并且从未修改过);-) 成功了。在 Timer 回调函数中调用 CFRunLoopStop(CFRunLoopGetCurrent()) 有效(“结束”打印)。但是在 NSURLSession dataTask 完成块中调用 CFRunLoopStop(CFRunLoopGetCurrent()) 似乎并没有停止 CFRunLoop(“结束”从不打印)。 我在 CLI 中将这段代码与 URLSession 完全结合使用。 @Rob 停止运行循环并调用 exit 的冗余可能是,可能只是让 CLI 保持某种状态的做法。 @vadian。我同意。那个while循环很丑陋。无需使用括号。【参考方案2】:

以下是使用 Swift 4.2 在 macOS 命令行工具中使用 URLSession 的方法

// Mac Command Line Tool with Async Wait and Exit
import Cocoa

// Store a reference to the current run loop
let runLoop = CFRunLoopGetCurrent()

// Create a background task to load a webpage
let url = URL(string: "http://SuperEasyApps.com")!
let task = URLSession.shared.dataTask(with: url)  (data, response, error) in
    if let data = data 
        print("Loaded: \(data) bytes")
    
    // Stop the saved run loop
    CFRunLoopStop(runLoop)

task.resume()

// Start run loop after work has been started
print("start")
CFRunLoopRun()
print("end") // End will print after the run loop is stopped

// If your command line tool finished return success,
// otherwise return EXIT_FAILURE
exit(EXIT_SUCCESS)

您必须在开始之前使用对运行循环的引用调用停止函数(如上所示),或者使用 GCD 以按预期退出。

func stopRunLoop() 
    DispatchQueue.main.async 
        CFRunLoopStop(CFRunLoopGetCurrent())
    

参考文献

https://developer.apple.com/documentation/corefoundation/1542011-cfrunlooprun

运行循环可以递归运行。您可以在任何运行循环调用中调用 CFRunLoopRun(),并在当前线程的调用堆栈上创建嵌套的运行循环激活。

https://developer.apple.com/documentation/corefoundation/1541796-cfrunloopstop

如果运行循环嵌套了一个激活的调用,启动另一个激活运行,则仅退出最里面的激活。

【讨论】:

【参考方案3】:

(回答我自己的问题)

将以下 sn-p 添加到我的异步网络完成代码允许打印“结束”:

DispatchQueue.main.async 
    shouldKeepRunning = false

【讨论】:

看起来我的原始代码在异步网络线程和主运行循环线程之间的线程间通信中存在线程错误。 这是我使用解决方案 #2 的最终工作代码:gist.github.com/hotpaw2/… 将 URLSession API 与完成处理程序一起使用比使用协议/委托要容易得多。 @vadian :长时间运行的 dataTask 的示例? (它用于来自非常慢的服务器的数据,其中数据会在数秒内滴落下来。) 没关系。委托方法仅在您需要处理凭据或显示进度条时才有用。

以上是关于如何退出运行循环?的主要内容,如果未能解决你的问题,请参考以下文章

Python问题求助--如何将循环程序中每次输入的数据循环使用,即退出当前循环程序后,再次运行该循环程序时可以使用原先输入的数据信息?

如何修复:Xcode 9 构建循环直到退出代码 255

Qt 循环中加入QCoreApplication::processEvents退出后台运行

Runloop(运行循环)是什么?

bat脚本命令循环运行程序 ,然后指定时间退出。

退出批处理文件中的 FOR 循环?