简体   繁体   English

在DispatchGroup中执行完成处理程序

[英]Execute Completion Handler in DispatchGroup

I want to show server(s) latency in a UITableView the only problem is ping result handler wont run under DispatchQueue 我想在UITableView显示服务器延迟,唯一的问题是ping结果处理程序不会在DispatchQueue下运行

let dispatch_queue = DispatchQueue(label: "PingQueue", qos: .background)
            dispatch_queue.async {
                let client = SimplePingClient()
                client.pingHostname(hostname: self.Servers[indexPath.row].Address, andResultCallback: { result in
                    guard let r = result as? String else {
                        DispatchQueue.main.async {
                            cell.lblLatency.text = "Timeout"

                        }
                        return
                    }
                    DispatchQueue.main.async{
                        cell.lblLatency.text = r + " ms"
                    }
                })
            }

the andResultCallback wont called at all (i dont know why !?) andResultCallback不会调用(我不知道为什么!?)

First, there's a few things you should note about your existing code. 首先,您应该注意一些有关现有代码的事情。

  1. Your call to dispatch_queue.async is doing the client initialization and the invocation of client.pingHostname run on the dispatch_queue.async queue. 您对dispatch_queue.async的调用正在执行client初始化,并且对client.pingHostname的调用在dispatch_queue.async队列上运行。 Note that because pingHostname is an asynchronous API, this returns almost immediately, and doesn't mean that the competition handler will run on dispatch_queue . 请注意,因为pingHostname是异步API,所以它几乎立即返回,并不意味着竞争处理程序将在dispatch_queue运行。

    • Note that using dispatch_queue.async probably doesn't make sense. 请注意,使用dispatch_queue.async可能没有任何意义。 Assuming client initialization is a lightweight, non-IO dependant task (ie it doesn't do any network calls of its own), and assuming pingHostname is lightweight (which it almost certainly is, given that it's an async function of its own), then there's no reason why you can't use a dispatch_queue.sync call, or even directly make these calls off your current thread. 假设客户端初始化是一个轻量级的,不依赖IO的任务(即,它不执行任何自身的网络调用),并假设pingHostname是轻量级的(鉴于它是其自身的异步功能,则几乎可以肯定是这样),那么就没有理由为什么不能使用dispatch_queue.sync调用,甚至不能直接从当前线程进行这些调用。 It'll probably be pretty much just as fast, or perhaps even faster (because async calls make escape analysis much harder, and limit compiler optimization). 它可能几乎一样快,甚至可能更快(因为异步调用使转义分析变得更加困难,并限制了编译器的优化)。 It has the added benefit of removing the worry of "what if this finished before this next thing? Or what if it's the other way around?". 这样做还有一个好处,那就是消除了“如果在下一个事情之前完成此操作?或者如果相反则要做什么?”的担心。
  2. You provide a closure to the asynchronous pingHostname API, which it has the freedom to run on any thread/queue it wishes, let's call it queue/thread X . 您提供了一个异步pingHostname API的闭包,它可以在所需的任何线程/队列上自由运行,我们称其为queue / thread X The API document will probably shed some light on this. API文档可能会对此有所启发。

  3. From the context of queue/thread X , you make async calls into DispatchQueue.main . 从队列/线程X的上下文中,您对DispatchQueue.main进行异步调用。 This is correct; 这是对的; Cocoa is designed to such that all UI updates must always happen from the main thread. 可可的目的是使所有UI更新必须始终在主线程中进行。

If your stated intention is to run competition handler code on dispatch_queue , then you have to either: 如果您声明的意图是在dispatch_queue上运行竞争处理程序代码,则您必须:

  1. Provide dispatch_queue as a parameter to the pingHostname API, so that it can run the competition handler on your queue, instead of whatever default it resorts to otherwise. 提供pingHostname API的dispatch_queue作为参数,以便它可以在您的队列上运行竞争处理程序,而不是采用其他默认值。 Of course, this is something the API must have been designed to have. 当然,这是API必须具有的功能。

  2. Make your own call to dispatch_queue.sync from thread/queue X from within the competition handler. 在竞争处理程序中,从线程/队列X调用自己的dispatch_queue.sync

In the absence of a DispatchQueue parameter, here is how I would write this: 在没有DispatchQueue参数的情况下,这是我的写法:

pingQueue = DispatchQueue(label: "PingQueue", qos: .background)

let client = SimplePingClient()
client.pingHostname(
    hostname: self.Servers[indexPath.row].Address, // This is jank, but I'm ignoring it for now
    andResultCallback: { _latency in
        pingQueue.sync {
            print("Do some stuff on pingQueue")

            DispatchQueue.main.sync { // enter main queue for UI updates
                cell.lblLatency.text = (_latency as? String).map { $0 + " ms" } ?? "Timeout"
            }
        }
    }
)

It's not right to call this inside cellForRowAt . cellForRowAt内部调用此方法是不正确的。 You should have somewhere in your ViewController array of results 您应该在ViewController结果数组中的某处

var results = [String]()

Now, get results somewhere else, not in cellForRowAt . 现在,在别的地方获取结果,而不是在cellForRowAt Create custom method for this, or use custom model. 为此创建自定义方法,或使用自定义模型。

Also better would be if your pingHostname method returned all results and then you just assign your results array and then reload data of your TableView 更好的方法是,如果您的pingHostname方法返回所有结果,然后您只分配results数组,然后重新加载TableView的数据

client.pingHostname { results in
    if let r = result as? [String] {
        self.results = r
        self.tableView.reloadData()
    } 
}

also use this results array as source for TableView's data source methods 也将此results数组用作TableView数据源方法的源

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return results.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    ...
    cell.lblLatency.text = results[indexPath.row]
    ...
}

The result callback isn't being called because your ping client is being deallocated before it's finished. 不会调用结果回调,因为在完成操作之前,您的ping客户端已被释放。

Make client an instance variable instead of a local variable. 使client成为实例变量而不是局部变量。 Set it to nil when you're done with it. 完成后将其设置为nil

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM