DispatchQueue 不等待异步函数完成

Posted

技术标签:

【中文标题】DispatchQueue 不等待异步函数完成【英文标题】:DispatchQueue does not wait for async function to complete 【发布时间】:2018-01-23 03:36:04 【问题描述】:

我正在尝试创建一个使用 USPS API 来获取数据的简单包裹跟踪应用程序。 callRestService 方法成功获取数据,并且其完成处理程序 serviceCallback(设置 unparsedXml 属性)工作。但是,我调用 callRestService 的方法在继续之前不会等待它及其完成处理程序完成,导致我的 print(unparsedXml) 语句返回 nil。

如下所示,我尝试使用 DispatchGroup 对象和 DispatchQueue 使函数等待 callRestService 完成,但无论如何它都会继续。如何让函数等待调用完成?

var unparsedXml:String?

public func getTrackingInfo(_ trackingNumber: String) -> TrackingInfo 
    let group = DispatchGroup()
    group.enter()
    DispatchQueue.global(qos: DispatchQoS.default.qosClass).async 
        self.callRestService(requestUrl: self.getRequest(trackingNumber))
        group.leave()
    
    group.wait()
    print(unparsedXml)
    return TrackingInfo()


private func getRequest(_ trackingNumber: String) -> String 
    let APIUsername = "Intentionally Omitted"
    let trackingXmlLink = "http://production.shippingapis.com/ShippingAPI.dll?API=TrackV2&XML=%3CTrackFieldRequest%20USERID=%22" + APIUsername + "%22%3E%20%3CRevision%3E1%3C/Revision%3E%20%3CClientIp%3E127.0.0.1%3C/ClientIp%3E%20%3CSourceId%3EFaiz%20Surani%3C/SourceId%3E%20%3CTrackID%20ID=%22" + trackingNumber + "%22%3E%20%3CDestinationZipCode%3E66666%3C/DestinationZipCode%3E%20%3CMailingDate%3E2010-01-01%3C/MailingDate%3E%20%3C/TrackID%3E%20%3C/TrackFieldRequest%3E"
    return trackingXmlLink


public func callRestService(requestUrl:String) ->Void

    var request = URLRequest(url: URL(string: requestUrl)!)
    request.httpMethod = "GET"

    let session = URLSession.shared
    let task = session.dataTask(with: request, completionHandler: serviceCallback)

    task.resume()


private func serviceCallback(data:Data? , response:URLResponse? , error:Error? ) -> Void

    unparsedXml = String(data: data!, encoding: .utf8)
    //print(unparsedXml) Works correctly when uncommented

【问题讨论】:

getRequest 将产生另一个异步任务,因此您的 group.leave 将立即执行。您应该在其中将完成处理程序传递给getRequestgroup.leave 对不起,我对此非常陌生,所以我不明白我会怎么做。能给我看看吗? 【参考方案1】:

您的问题是callRestService 将调度异步网络操作,因此您的group.leave 将立即被调用,触发您的group.notify

您可以将group.leave 放在完成处理程序中,但您应该避免阻塞代码。我建议您将 getTrackingInfo 构建为采用完成处理程序的异步函数:

public func getTrackingInfo(_ trackingNumber: String, completion:(TrackingInfo?,Error?) -> Void) 
    self.callRestService(requestUrl: self.getRequest(trackingNumber))  (data, response, error) in
        guard error == nil, let returnData = data else 
            completion(nil,error)
            return
        

        completion(TrackingInfo(returnData),nil)
    


private func getRequest(_ trackingNumber: String) -> String 
    let APIUsername = "Intentionally Omitted"
    let trackingXmlLink = "http://production.shippingapis.com/ShippingAPI.dll?API=TrackV2&XML=%3CTrackFieldRequest%20USERID=%22" + APIUsername + "%22%3E%20%3CRevision%3E1%3C/Revision%3E%20%3CClientIp%3E127.0.0.1%3C/ClientIp%3E%20%3CSourceId%3EFaiz%20Surani%3C/SourceId%3E%20%3CTrackID%20ID=%22" + trackingNumber + "%22%3E%20%3CDestinationZipCode%3E66666%3C/DestinationZipCode%3E%20%3CMailingDate%3E2010-01-01%3C/MailingDate%3E%20%3C/TrackID%3E%20%3C/TrackFieldRequest%3E"
    return trackingXmlLink


public func callRestService(requestUrl:String, completion:(Data? , URLResponse? , Error? ) -> Void) ->Void

    var request = URLRequest(url: URL(string: requestUrl)!)
    request.httpMethod = "GET"

    let session = URLSession.shared
    let task = session.dataTask(with: request, completionHandler: completion)

    task.resume()

【讨论】:

【参考方案2】:

notify中等待操作完成 模拟一个网络请求:

public func networkTask(label: String, cost: UInt32, complete: @escaping ()->()) 
    NSLog("Start network Task task%@",label)
    DispatchQueue.global().async 
        sleep(cost)
        NSLog("End networkTask task%@",label)
        DispatchQueue.main.async 
            complete()
        
    


let group = DispatchGroup()

group.enter()
networkTask(label: "2", cost: 4) 
  group.leave()

group.enter()
networkTask(label: "1", cost: 3) 
  group.leave()


group.notify(queue: .main) 
 print("task complete!")
 // ......

你可以试试这个例子。

【讨论】:

我认为DispatchGroup就像ARCenter()+1leave()-1,当等于0时,会调用notify

以上是关于DispatchQueue 不等待异步函数完成的主要内容,如果未能解决你的问题,请参考以下文章

在映射下一项之前,异步等待映射不等待异步函数在映射函数内部完成

如果任务产生不同的线程,Serial DispatchQueue 会等待吗

异步等待不等待完成

等待异步函数完成而不添加回调

等待 Dart 异步函数完成

等待两个异步完成函数完成,然后再执行下一行代码