swift - 进程运行时 Mac Cocoa 应用程序 GUI 卡住

标签 swift multithreading cocoa xcode9 freeze

我在 Xcode 9 中使用 Swift 制作了一个基本的 Mac OS X Cocoa 应用程序。该应用程序从用户那里收集源和目标,然后将数据从源传输到目标。数据传输是通过将 rsync 脚本作为进程启动来完成的:

let path = "/bin/bash"
let arguments = ["/path/to/backup.sh", sourcePath, destinationPath]
task = Process.launchedProcess(launchPath: path, arguments: arguments as! [String])

问题是,当传输正在运行时,应用程序会旋转彩虹轮并且无法使用 GUI。这使得“取消”按钮或功能进度条变得不可能。

传输完成后,应用程序将再次运行,并且所有测试输出(例如,打印语句、进度条)都会通过(而不是像预期的那样在备份运行时通过)。

我认为线程可能有助于解决这个问题,所以我查看了这篇关于 Apple 线程的帖子:https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html

但是,文章中的代码在 Xcode 中似乎没有正确的语法,所以我不确定如何继续使用线程。

我正处于死胡同,任何帮助将不胜感激!

运行应用程序后,在调试器中暂停,并在调试器控制台中键入“bt”,这是输出:

* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
frame #0: 0x00007fff65fc120a libsystem_kernel.dylib`mach_msg_trap + 10
frame #1: 0x00007fff65fc0724 libsystem_kernel.dylib`mach_msg + 60
frame #2: 0x00007fff3dac4045 CoreFoundation`__CFRunLoopServiceMachPort + 341
frame #3: 0x00007fff3dac3397 CoreFoundation`__CFRunLoopRun + 1783
frame #4: 0x00007fff3dac2a07 CoreFoundation`CFRunLoopRunSpecific + 487
frame #5: 0x00007fff3cda0d96 HIToolbox`RunCurrentEventLoopInMode + 286
frame #6: 0x00007fff3cda0b06 HIToolbox`ReceiveNextEventCommon + 613
frame #7: 0x00007fff3cda0884 HIToolbox`_BlockUntilNextEventMatchingListInModeWithFilter + 64
frame #8: 0x00007fff3b053a73 AppKit`_DPSNextEvent + 2085
frame #9: 0x00007fff3b7e9e34 AppKit`-[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 3044
frame #10: 0x00007fff3b048885 AppKit`-[NSApplication run] + 764
frame #11: 0x00007fff3b017a72 AppKit`NSApplicationMain + 804
* frame #12: 0x000000010000a88d Mac Syncy`main at AppDelegate.swift:12
frame #13: 0x00007fff65e7a015 libdyld.dylib`start + 1
frame #14: 0x00007fff65e7a015 libdyld.dylib`start + 1

最佳答案

所以,我发现了几件事:

  1. 事实上,launchedProcess 会导致进程立即启动。相反,使用 init 获得更多控制权
  2. 如果调用waitForExit,则当前线程将等待直到进程结束。
  3. 当您启动一个进程时,它将独立于您的应用程序运行。因此,如果您退出您的应用,已启动的进程仍会继续运行。

那么让我们开始吧(在最后完全工作的 View Controller ):

1。创建流程

task = Process.init()
task.launchPath = path
task.arguments = arguments
task.currentDirectoryPath = workDir
// not running yet, so let's start it:
task.lauch()

2。异步等待子进程结束

DispatchQueue.global().async {
    print ("wating for exit")
    self.task.waitUntilExit()
}

3。杀死子进程

您必须终止任务,例如在应用程序委托(delegate)中的 applicationWillTerminate 中。

然而,您应该意识到这可能会导致您的 (rsync) 操作处于不确定状态 - 文件/目录仅被复制了一半等。

4。奖励:进度指示器

我认为提供进度指示器的唯一方法是解析进程的输出 (task.standardOutput) 并检查 rsync 是否在此处提供有用的信息。但这是一个全新的故事,所以这里没有代码,抱歉。

代码

这是一个带有开始和取消按钮的 View Controller 。 请记住,对于已发布的应用程序,您必须提供更多的错误检查。

class ViewController: NSViewController {

    var task:Process!
    var out:FileHandle?
    var outputTimer: Timer?


    @IBAction func startPressed(_ sender: NSButton) {
        print("** starting **")

        let path = "/bin/bash"
        let workDir = "/path/to/working/folder"
        let sourcePath = "source"
        let destinationPath = "destination"

        let arguments = ["backup.sh", sourcePath, destinationPath]
        task = Process.init()
        task.launchPath = path
        task.arguments = arguments
        task.currentDirectoryPath = workDir

        self.task.launch()
        DispatchQueue.global().async {
            // this runs in a worker thread, so the UI remains responsive
            print ("wating for exit")
            self.task.waitUntilExit()
            DispatchQueue.main.async {
                // do so in the main thread
                if let timer = self.outputTimer {
                    timer.invalidate()
                    self.outputTimer = nil
                }
            }
        }

    }


    @IBAction func cancelPressed(_ sender: NSButton) {
        print("** cancelling **")
        if let timer = self.outputTimer {
            timer.invalidate()
            self.outputTimer = nil
        }
        task.interrupt()
    }
}

关于swift - 进程运行时 Mac Cocoa 应用程序 GUI 卡住,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51092442/

相关文章:

ios - 如何将 String 转换为 JSON,以便我可以将其解析为对象

c# - Azure Web 作业 - 在云中实现互斥

cocoa - NSUndoManager、核心数据和选择性撤消/重做

objective-c - Objective C 项目组织

objective-c - 使用 WebView 将 HTML 加载到 Cocoa 应用程序中并更改文本

iOS:跨viewControllers在导航栏下方添加 View

swift - 如何清除 AlamofireImage setImageWithURL 缓存

Xcode 不会自动完成结构的成员初始化程序

c++ - std::map 和 std::vector 线程安全吗?

spring - 为什么不跨线程共享SecurityContext的Authentication对象?