为调用异步方法的异步调度队列实现完成处理程序 [英] implementing a completion handler for an asynchronous dispatch queue which calls asynchronous methods

查看:74
本文介绍了为调用异步方法的异步调度队列实现完成处理程序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个performSync函数,该函数通过一个数组运行,对于该数组中的每个项目,我正在调用一个函数,该函数本身包含一个异步alamofire请求.我需要能够知道外部函数何时已完成在for循环中运行所有函数,因此我需要添加一个完成处理程序.我不确定如何实现.

I have a function performSync which runs through an array and for each item in this array, I am calling a function which in itself contains an async alamofire request. I need to be able to tell when this outer function has completed running all functions within the for loop, so I need to add a completion handler. I am unsure how to implement this.

此外,在我的for循环中,我使用.userinitiated来尝试不阻塞ui线程,但是该线程被阻塞了.我还尝试使用注释掉的并发队列方法,但这也阻止了UI.

Also, in my for loop, I have used .userinitiated in order to try and not block the ui thread, however the thread is being blocked. I also tried using the commented out concurrent queue method, but that also blocked the UI.

以下代码:

public class Sync {
        public class func onPerformSync(finished: () -> Void){

        let syncList = ['Process1', 'Process2', 'Process3']
        //let concurrentQueue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent)
        for item in syncList {
             //concurrentqueue.async
             DispatchQueue.global(qos: .background).async {
                 let className = "appname."+item
                 let ClassObj = NSClassFromString(className)! as! Helper_Base.Type
                 ClassObj.doSync()
             }
            //multiple classes extend Helper_Base and override the  doSync func  
            //I realise there may be a swiftier way to call these doSync methods rather than instantiating from a string and overriding the base class 
           //doSync and I'd welcome advice on it!
        }
        //calling finished here is fine if I use a synchronous dispatchQueue but where do i place this line since I need my queue to be asynchronous?
        finished()
    }
}

open class Process1 : Helper_Base {
    override open class func doSync(){
        let time = Int64(NSDate().timeIntervalSince1970 * 1000)
        repeatBlock(time: time)
    }

    open class func repeatBlock(time : Int64){
        let parameters : [String : String] = [
            "x" : time
        ]
        var continueSync : Bool = false
        DispatchQueue.global(qos: .background).async {
        Alamofire.request(url, method: .post, parameters: parameters, encoding: URLEncoding.default)
            .response { response in
                if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
                    guard let utf8TextDecoded = utf8Text.fromBase64() else {
                        return
                    }

                    let error = response.error
                    if error == nil
                    {
                        do {
                            let json = try JSONSerializer.toDictionary(utf8TextDecoded)
                            let more = json["more"] as! Bool

                            continueSync = more
                        }
                    }
                }
                if continueSync {
                    let time = Int64(NSDate().timeIntervalSince1970 * 1000)
                    DispatchQueue.global(qos: .background).async {
                        repeatBlock(time: time)
                    }
                }
                else{
                   finishSync()
                }
        }

    }
    }

    open class func finishSync(){
        //other stuff
    }
}


Sync.onPerformSync(){
    print("we're done syncing")
}

推荐答案

一些观察结果:

  1. 在回答您的主要问题时,如何让 finishSync 通知其调用者已完成,您可能会重构 doSync 以采用完成处理程序.这将(由于它正在执行递归网络调用的事实)建议您可以移至实例方法并保存 completionHandler :

  1. In answer to your main question, how to have finishSync inform its caller that it was done, you'd probably refactor doSync to take a completion handler. This would (because of the fact that it's doing recursive network calls) suggest that you might move to instance methods and save the completionHandler:

public class HelperClass {

    private var completionHandler: (() -> Void)?

    open func doSync(item: String, completionHandler: @escaping () -> Void) {
        guard self.completionHandler == nil else {        // make sure the existing completionHandler is nil
            fatalError("Either use another instance or let this one finish first")
        }

        self.completionHandler = completionHandler        // save the completion handler

        repeatBlock()                                     // start your recursive code
    }

    open func repeatBlock() {                             // do your recursive stuff
        // do something
    }

    open func finishSync(){
        // other stuff

        completionHandler?()                              // call the saved completion handler
        completionHandler = nil                           // and discard it, removing any strong references, if any
    }
}

  • 这仅是一个问题,即您如何确定 process1 process2 等的顶级 for 循环.,不会同时运行这些循环.坦白地说,您通常会希望同时运行(因为要付出巨大的性能代价才能依次运行请求),但是如果您不这样做,则必须将其包装在另一个递归请求中在异步 Operation 自定义子类中处理或包装全部内容.

  • This only then begs the question of how you make sure that top level for loop for process1, process2, etc., doesn't run these loops concurrently. Frankly, you generally would want it to run concurrently (because you pay huge performance penalty to run requests sequentially), but if you don't, you'd either have to wrap this in yet another recursive requesting process or wrap the whole thing in an asynchronous Operation custom subclass.

    关于您的用户界面被阻止的原因,目前尚不清楚.如果有的话,您甚至不需要将 doSync 分派到某个全局队列,因为Alamofire是异步的.分派已经与后台队列异步的呼叫没有任何意义.

    Regarding why your UI is blocked, that's less clear. If anything, you don't even need to dispatch the doSync to some global queue, because Alamofire is asynchronous. There's no point in dispatching a call that is already asynchronous to a background queue.

    唯一可疑的是从 repeatBlock 返回到 repeatBlock 的递归调用.如果Alamofire调用不是异步运行的(即它返回了缓存的结果或出现了一些错误),那么从理论上讲,您可以在Alamofire使用的队列上旋转.我建议使用 async repeatBlock 内部调度对 repeatBlock 的递归调用,以避免任何潜在的问题.

    The only thing that looks suspicious is the the recursive call from repeatBlock back to repeatBlock. If the Alamofire call doesn't run asynchronously (i.e. it returned cached results or there's some error), you could theoretically spin on the queue that Alamofire is using. I'd suggest dispatching that recursive call to repeatBlock from within repeatBlock with async, to avoid any potential problems.

    您可以做的另一件事是向Alamofire request 提供 queue 参数,以确保它不使用主队列.它留给自己的设备,在主队列上调用其完成处理程序(通常非常有用,但可能会导致您的方案出现问题).我建议尝试提供一个非主队列作为 request queue 参数.

    The other thing you can do is to supply the queue parameter to the Alamofire request, ensuring it's not using the main queue. Left to its own devices, it calls its completion handlers on the main queue (which is generally quite useful, but could cause problems in your scenario). I would suggest trying supplying a non-main queue as the queue parameter of request.

    如果它仍然处于阻塞状态,建议您通过Instruments的系统跟踪运行它,并查看主队列上存在哪些阻塞调用.或者,您也可以运行该应用程序,在冻结UI时暂停它,然后查看主线程的堆栈跟踪,然后可能会看到它在哪里阻塞.

    If it's still blocking, I'd suggest either running it through the system trace of Instruments and see what blocking calls there are on the main queue. Or you can also just run the app, pause it while the UI is frozen, and then look at the stack trace for the main thread, and you might see where it's blocking.

    最后,我们必须考虑以上代码中没有阻塞主队列的可能性.假设您没有遇到Alamofire立即调用其完成处理程序的退化情况,则上述代码似乎不太可能阻塞主队列.上面的诊断程序可以确认这一点,但是我建议您扩大搜索范围,以找出其他会阻塞主队列的东西:

    Finally, we must contemplate the possibility that the blocking of the main queue does not rest in the above code. Assuming that you're not suffering from degenerate situation where Alamofire is calling its completion handler immediately, the above code seems unlikely to block the main queue. The above diagnostics should confirm this, but I might suggest you broaden your search to identify other things that would block the main queue:

    • 从串行队列(例如主队列)到串行队列的任何 sync 调用都可能是问题.
    • 或者任何锁,信号量或调度组等待也可能是候选对象.
    • 您具有既不递归调用 repeatBlock 也不调用 finishSync 的路径...您确定主队列被阻塞了,并且从不永不调用它 finishSync 在某些执行路径中.
    • Any sync calls from a serial queue (such as the main queue) to a serial queue are a likely problem.
    • Or any locks, semaphores, or dispatch group waits are also likely candidates.
    • You have paths where you neither recursively call repeatBlock nor call finishSync ... are you sure the main queue is blocked and it's not just a matter that it's never calling finishSync in some paths of execution.
       

    最重要的是,请确保问题实际上出在上述代码中的阻塞主线程上.我怀疑可能不会.

    Bottom line, make sure the problem actually rests in blocking the main thread in the above code. I suspect it might not.

    为了以一些不请自来的建议结束,并且没有任何故意的冒犯,我们应该承认,在这一系列的网络请求中,有一种代码味道的提示.如果要返回更多数据,请更改Web服务API以返回更多数据...这种重复,系统地获取附加数据(特别是按顺序完成时)的效率极低.网络延迟将使整体性能(即使在解决了此主队列阻塞问题之后)也确实受到了影响.

    To close with a bit of unsolicited advice, and with no offense intended, we should acknowledge that there is a hint of a code smell in this flurry of network requests. If you want to return more data, change the web service API to return more data ... this repeated, systematic fetching of additional data (especially when done sequentially) is horribly inefficient. The network latency is going to make overall performance (even after you solve this main queue blocking problem) really suffer.

    这篇关于为调用异步方法的异步调度队列实现完成处理程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆