首页 > 解决方案 > 你能有同步但非阻塞的 URLSessions 吗?

问题描述

我正在使用本质上是异步的 URLSessions。当会话只有一个呼叫时,这很有效。当我需要执行多个(串行)调用时,需要按执行顺序组合结果,这会使程序逻辑痛苦且容易出错。我也阻塞了主线程,这不好。

约束

移动到下一个任务可能不会在 4 秒过去之前发生,但可能会更长。

利用 Monterey(需要操作系统升级),因此使用 let (data, response) = try await session.data(from: url) 不是可选的。

如果任务的执行速度快于每 4 秒,则结果是服务器端错误,强制重试。

任务执行

我当前的进程使用信号量或 dispatchGroups 但都阻塞了主线程。

有没有办法在不阻塞主线程的情况下获得同步行为?

    func getDataFromInput_Sync(authToken: String, transformedText: String ) -> (Data?, URLResponse?, Error?)
    {
        /*
         By default, transactions are asynchronous, so they return data while the rest of the program continues.
         Changing the behavior to synchronous requires blocking to wait for the outcome.  It affords us the
         ability to manage program flow inline.  This methods forces synchronous behavior and the output, which
         are returned via a tuple, types: (Data?, URLResponse?, Error?)
         */
        
        
        var outData       : Data?
        var outError      : Error?
        var urlResponse   : URLResponse?
        let targetURL     = "https://..." 
        let urlconfig     = URLSessionConfiguration.ephemeral
       
        // set the timeout to a high number, if we are prepared to wait that long.  Otherwise the session will timeout.
        urlconfig.timeoutIntervalForRequest  = 120
        urlconfig.timeoutIntervalForResource = 120

        let urlSession    = URLSession(configuration: urlconfig)
//        let dispatchGroup = DispatchGroup()
        let semaphore     = DispatchSemaphore(value: 0)

        // ephermeral doesnt write cookies, cache or credentials to disk
                
        guard let url = URL(string: targetURL),
              let httpBodyData = transformedText.data(using: .utf8) else { return (nil,nil,nil) }
        
        var request = URLRequest(url: url)
        
        request.httpMethod = "POST"
        request.httpBody   = httpBodyData
        request.addValue("Token " + authToken, forHTTPHeaderField: "Authorization")
        
        // Perform HTTP Request
        let task = (urlSession).dataTask(with: request) { (data, response, error) in
            guard error == nil
            else {
                print("we have an error: \(error!.localizedDescription)")
                return
            }
            
            guard let data = data else { print("Empty data"); return }
            
            outData     = data
            urlResponse = response
            outError    = error
//            dispatchGroup.leave()
            semaphore.signal()

        }
        
        task.resume()
        semaphore.wait()

//        dispatchGroup.enter()
//        task.resume()
//        dispatchGroup.wait()
        
        return (outData, urlResponse, outError)
    }

    func testServerRequest()
    {
        let sentences   = ["Example Sentence1","Example Sentence2","Example Sentence3"] //...array to process
        for (_,thisString) in sentences.enumerated()
        {
            let timeTestPoint   = Date()
            let futureSecs      = 4.0
            let (data, urlResponse, error) = getDataFromInput_Sync(authToken: authToken, transformedText: thisString )
            let elapsed         = timeTestPoint.timeIntervalSinceNow * -1 // time elapsed between request and result

            // act on the data received
            
            // executing the next request before futureSecs will cause an error, so pause
            let delayAmt = futureSecs - elapsed
            Thread.sleep(forTimeInterval: delayAmt)
        }
    }

标签: swiftconcurrencynsurlsessionnsurlrequest

解决方案


让它在后台队列中,比如

DispatchQueue.global(qos: .background).async {
    testServerRequest()
}

推荐阅读