在分派到主线程时返回值

13

我有一个函数func getValue() -> Bool,它是从后台线程调用的。这是有意为之的,也是必须的。现在,getValue()需要在主线程上执行某些操作,在这种情况下,它需要访问UIApplication.shared.canOpenURL,这必须在主队列上运行。

这是我的当前函数:

func getValue() -> Bool {
    guard let url = URL(string: "someurl") else { return false }
    return UIApplication.shared.canOpenURL(url)
}

如何将该函数转换为线程安全的函数,即确保它始终在主线程上运行,而不需要:

  • 从一开始就从主线程调用函数
  • 重构函数以在闭包中返回值

我尝试过这样做:

// This causes a deadlock, see https://dev59.com/TVgQ5IYBdhLWcg3wnVR3#42484670
func getValue() -> Bool {
    var flag = false

    let group = DispatchGroup()
    group.enter()
    DispatchQueue.main.async {
        if let url = URL(string: "someurl"), UIApplication.shared.canOpenURL(url) {
            flag = true
        }
        group.leave()
    }
    group.wait()

    return flag
}

并且这个:

// This crashes with EXC_BREAKPOINT (SIGTRAP) dispatch_sync called on queue already owned by current thread
func getValue() -> Bool {
    return DispatchQueue.main.sync {
        guard let url = URL(string: "someurl") else { return false }
        return UIApplication.shared.canOpenURL(url)
    }
}

但是它们两个都不起作用。有什么想法吗?


如果您的意思是这样的 `func getValue() -> Bool { var flag = falseDispatchQueue.main.async { if let url = URL(string: "someurl"), UIApplication.shared.canOpenURL(url) { flag = true } } return flag}` 那么这也行不通,因为调度异步内部的代码永远不会被执行,因为函数已经返回了一个值。 - imas145
好的,你没有进行任何UI活动,所以我认为在不使用任何主队列的情况下,你可以做到这一点?你试过这个吗? - vivekDas
当然,如果我认为代码很重要,我会包含它的。我曾经错误地认为你不能在主线程上调用同步,所以我没有太关注我已经预料到的崩溃。无论如何,就像我说的,我添加了一个线程检查,一切似乎都正常工作了。非常感谢您的帮助,但不需要这种语气。 - imas145
没错,但是“被认为”和“错误观念”在这里很关键。“被认为”表明它不是一个MCVE,而“错误观念”则表明它是一个x-y问题。我只是想指出更好的提问方式。 - matt
@matt 你说得对,这不是一个好问题,我现在更明白了。 - imas145
显示剩余5条评论
2个回答

12

你正在寻找信号量 - 试试这个:

DispatchQueue.global(qos: .background).async {
    var value: Bool? = nil
    let semaphore = DispatchSemaphore(value: 0)
    DispatchQueue.main.async {
        let alert = UIAlertController(title: "Choose one", message: "Take your time, I'll wait", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "true", style: .default, handler: { _ in
            value = true
            semaphore.signal()
        }))
        alert.addAction(UIAlertAction(title: "false", style: .default, handler: { _ in
            value = false
            semaphore.signal()
        }))
        self.present(alert, animated: true, completion: nil)
    }
    semaphore.wait()
    print("Choice: \(value!)")
}

或者使用您上面的例子:

func getValue() -> Bool {
    var flag = false
    let semaphore = DispatchSemaphore(value: 0)

    DispatchQueue.main.async {
        if let url = URL(string: "someurl"), UIApplication.shared.canOpenURL(url) {
            flag = true
            semaphore.signal()
        }
    }
    semaphore.wait()
    return flag
}

1
如果在主线程上调用 getValue(),将会产生死锁。 - ScottyBlades

9

我无法复现你第二个示例中的任何问题。由于你没有展示如何调用getValue,因此我做了一些猜测:

func getValue() -> Bool {
   return DispatchQueue.main.sync {
        guard let url = URL(string: "testing://testing") else { return false }
        return UIApplication.shared.canOpenURL(url)
   }
}
override func viewDidLoad() {
    super.viewDidLoad()
    DispatchQueue.global(qos:.background).async {
        let ok = self.getValue()
        print(ok) // false, the right answer
    }
}

没有“崩溃”,所以我只会继续使用它。 当我使用方案testing:时,我会得到false的结果,当我将testing:更改为https:时,我返回了true,因此方法调用显然是有效的。

DispatchQueue.main.sync会导致死锁崩溃吗? - Akshansh Thakur
4
在我的情况下,我遇到了一个崩溃 EXC_BREAKPOINT (code=1, subcode=0x10756a84c)。需要检查if Thread.current.isMainThread来防止崩溃。 - Nike Kov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接