当主线程等待它运行时,Alamofire 从不调用 encodingCompletion 以使用 MultipartFormData 上传

Posted

技术标签:

【中文标题】当主线程等待它运行时,Alamofire 从不调用 encodingCompletion 以使用 MultipartFormData 上传【英文标题】:Alamofire never calls encodingCompletion for upload with MultipartFormData when main thread is waiting for it to run 【发布时间】:2017-03-07 09:06:17 【问题描述】:

我有这种形式的代码:

func myFunction(<...>, completionHandler: (ResponseType) -> Void) 
    <prepare parameters>

    mySessionManager.upload(multipartFormData: someClosure,
        to: saveUrl, method: .post, headers: headers)  encodingResult in
          // encodingCompletion
          switch encodingResult 
                case .failure(let err):
                    completionHandler(.error(err))
                case .success(let request, _, _):
                    request.response(queue: self.asyncQueue)  response in
                        // upload completion
                        <extract result>
                        completionHandler(.success(result))
                    
            
     

并像这样测试代码:

func testMyFunction() 
    <prepare parameters>

    var error: Error? = nil
    var result: MyResultType? = nil

    let sem = DispatchSemaphore(value: 0)
    var ran = false
    myFunction(<...>)  response in
        if ran  
            error = "ran twice"
            return
        
        defer 
            ran = true
            sem.signal()
        

        switch response 
            case .error(let err): error = err
            case .success(let res): result = res
        
    
    sem.wait()

    XCTAssertNil(error, "Did not want to see this error: \(error!)")

    <test response>

我使用信号量阻塞主线程,直到请求被异步处理;这适用于我所有其他的 Alamofire 请求——但不是这个。测试挂起。 (注意:使用active waiting 不会改变任何事情。)

使用调试器,我发现

所有执行的代码都很好,但是 encodingCompletion is never called.

现在我最好的猜测是DispatchQueue.main.async 说,“有时间的时候在主线程上执行它”——它永远不会,因为我的测试代码在那里阻塞(并且会运行进一步的测试,无论如何)。

我用self.queue.asyncupload.delegate.queue.addOperation 替换了它,这两个队列操作在同一个函数中找到。然后测试通过但产生意外错误;我的猜测是,encodingCompletion 被称为为时过早

这里有几个问题要问;任何问题的答案都可以解决我的问题。

    我能否以不同的方式测试此类代码,以便 DispatchQueue.main 可以执行其他任务? 如何使用调试器找出运行的线程是什么时候? 如何调整Alamofire at the critical position 使其不需要主队列?

【问题讨论】:

【参考方案1】:

正如here 解释的那样,这是一个糟糕的“解决方案”,因为它在请求嵌套时引入了死锁的可能性。出于教学目的,我将其留在这里。


变化

DispatchQueue.main.async 
    let encodingResult = MultipartFormDataEncodingResult.success(
            request: upload,
            streamingFromDisk: true,
            streamFileURL: fileURL
        )

    encodingCompletion?(encodingResult)

在SessionManager.swift到

self.queue.sync 
    ...

解决(阅读:解决)问题。

我不知道这是否是一个强大的修复或任何东西;我有filed an issue。

【讨论】:

【参考方案2】:

我们不应该阻塞主线程。 XCTest 有自己的等待异步计算的解决方案:

let expectation = self.expectation(description: "Operation should finish.")
operation(...)  response in
    ...
    expectation.fulfill()

waitForExpectations(timeout: self.timeout)

来自the documentation:

在处理事件时运行运行循环,直到满足所有期望或达到超时。客户端在使用此 API 时不应操纵运行循环。


在 XCTest 之外,我们可以使用与 XCTestCase.waitForExpectations() 类似的机制:

var done = false
operation(...)  response in
    ...
    done = true


repeat 
    RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1))
 while !done

注意: 这假设operation 将其工作发送到同一个队列本身执行。如果它使用另一个队列,这将不起作用;但随后使用DispatchSemaphore(见the question)的方法不会导致死锁,可以使用。

XCTest 中的实现还有很多功能(多重期望、超时、可配置的睡眠间隔等),但这是基本机制。

【讨论】:

以上是关于当主线程等待它运行时,Alamofire 从不调用 encodingCompletion 以使用 MultipartFormData 上传的主要内容,如果未能解决你的问题,请参考以下文章

当主线程无限循环运行时,服务器没有收到用 QTcpSocket::write 写入的字节?

强制 Alamofire 在主线程上做所有事情?

谁打断了我的话题?

QT 线程从不运行

JavaScript 运行机制详解

通过等待条件等待多个正在运行的线程