DispatchQueue.main.async 挂起后重复,但使用睡眠时不挂起

Posted

技术标签:

【中文标题】DispatchQueue.main.async 挂起后重复,但使用睡眠时不挂起【英文标题】:DispatchQueue.main.asyncAfter hanging on repeat, but does not hang when using sleep 【发布时间】:2021-11-28 13:53:04 【问题描述】:

我正在尝试使用 Swift 为 Macos 创建一个机器人流程自动化工具。用户创建一个自动化,它是一系列 Step 对象,然后播放它。 Step 的子类之一是 Pause,它应该暂停执行给定的秒数。

由于某种原因,当我在 Pause 类中使用 DispatchQueue.main.asyncAfter() 方法时,执行挂起。通常第一次运行自动化是好的,但是当它重复时,它最终会挂起更长的时间。当我改用sleep() 时,错误消失了。

关于这个错误的另一个奇怪的事情是,当我打开 Xcode 尝试查看发生了什么时,挂起解决并继续执行。我想知道该进程是否以某种方式进入后台,然后 DispatchQueue.main.asyncAfter() 不起作用。我尝试将 Info.plist “应用程序不在后台运行”更改为 YES,但这没有任何效果。

使用sleep() 的问题是它会阻塞 UI 线程,因此用户无法在需要时停止自动化。我用 DispatchQueue 尝试了许多不同的线程变体,但它似乎总是在重复执行时挂在某个地方。我也尝试过使用 Timer.scheduledTimer() 而不是 DispatchQueue 但这也挂起。我确定我错过了一些简单的东西,但我无法弄清楚。

创建步进数组并启动自动化

class AutomationPlayer 
  
  static let shared = AutomationPlayer()
  
  var automation: Automation?
  var stepArray: [Step] = []
  
  func play() 
    // Create array of steps
    guard let steps = automation?.steps, let array = Array(steps) as? [Step] else  
      return 
    
    // If the automation repeats, add more steps to array.
    for _ in 0..<(automation?.numberOfRepeats ?? 1) 
      for (index, step) in array.enumerated() 
          stepArray.append(step)
      
    
    // Add small delay to allow window to close before execution.
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.4)  [weak self] in
      self?.execute(index: 0)
    
  
  
  private func execute(index: Int) 
    let step = stepArray[index]
    
    executeStep(step: step)  [weak self] success, error in
      guard error == nil else  return 
      let newIndex = index + 1
      if newIndex < self?.stepArray.count ?? 0 
        //Need a small delay between steps otherwise execution is getting messed up.
        usleep(400000)
        self?.execute(index: newIndex)
       else 
        self?.stepArray = []
      
    
  
  
  private func executeStep(step: Step?, completionHandler: @escaping (Bool, Error?) -> Void) -> Void 
    step?.execute(completionHandler:  [weak self] success, error in
      guard error == nil else 
        completionHandler(false, error)
        return
      
      completionHandler(true, nil)
    )
  

暂停课

@objc(Pause)
public class Pause: Step 
  
  override func execute(completionHandler: @escaping (Bool, Error?) -> Void)  
    print("Pause for: \(self.time) seconds")

    // This will eventually hang when the automation repeats itself
    DispatchQueue.main.asyncAfter(deadline: .now() + Double(self.time)) 
      completionHandler(true, nil)
    )

    // This will also hang
    Timer.scheduledTimer(withTimeInterval: self.time, repeats: false)              timer in
      completionHandler(true, nil)
    

    // If I use this instead, the automation repeats just fine
    sleep(UInt32(self.time))
    completionHandler(true, nil)

  

【问题讨论】:

我尝试使用 Timer,但在第一次重复后它仍然挂起。 这里发生了很多非常时髦的事情。我建议您将其交叉发布到 codereview.stackexchange.com 【参考方案1】:

所以我想我想通了。 MacOS 在一段时间后将我的应用程序放入 AppNap,这将导致 DispatchQueue.main.async() 停止工作。由于某些原因,AppNap 不会影响您使用sleep()时的延迟

我找到了答案here

这个答案有点老了。我正在使用 SwiftUI 构建我的 mac 应用程序,所以我添加了这个我的 @main 结构

@main
struct Main_App: App 

   @State var activity: NSObjectProtocol?

   var body: some Scene 
      WindowGroup("") 
         MainWindow()
        .onAppear 
           activity = ProcessInfo().beginActivity(options: .userInitiated, reason: "Good Reason")
        
    

这似乎阻止了应用程序进入 AppNap 并且自动化继续进行。这很丑陋,但它确实有效。

【讨论】:

以上是关于DispatchQueue.main.async 挂起后重复,但使用睡眠时不挂起的主要内容,如果未能解决你的问题,请参考以下文章

为啥 DispatchQueue.main.async 会影响单元格自动调整大小?

SwiftUI - HealthKit 中的 DispatchQueue.main.async

SwiftUI - HealthKit 中的 DispatchQueue.main.async

Swift 5:我无法让我的 UITableView 及时更新(同时使用 `DispatchQueue.global().sync` 和 `DispatchQueue.main.async`

在 DispatchQueue.main.async 上运行代码是不是更慢?

DispatchQueue.main.async 的初学者问题