如何处理流响应异步

时间:2018-03-07 06:07:27

标签: swift asynchronous grand-central-dispatch

我打开了一个流到我的服务器。当我发送文本“PING”时,它以“PONG”响应。我已成功连接,发送消息和收到回复。我在下面进行了相当简单的骨骼测试。

问题
我想在他们进来时处理来自服务器的消息。目前,我发送三个PING,后来我得到三个PONGS。服务器立即响应PONG但我的代码在主线程完成之后才处理响应。预期的结果是在PING发送后立即看到PONG消息,因为它们是同时处理的。

我尝试了什么
差不多,你在下面看到的。我想“我想在发送消息的同时处理流响应,所以我需要在另一个线程上执行此操作”。所以,我通过GCD将RunLoop放在另一个线程中。这没有帮助....无法想象我们如何在单独的线程中使StreamDelegate处理其委托方法stream ...

当前的控制台结果

PING
PING
PING
PONG
PONG
PONG

所需的控制台结果

PING
PONG
PING
PONG
PING
PONG

守则

import Foundation
import XCTest

class StreamTests: XCTestCase, StreamDelegate {

    var inputStream: InputStream?
    var outputStream: OutputStream?

    let url: URL = URL(string: "http://theserver.com:4222")!

    func testAsyncStream() {

        self.setupStream()

        let ping = "PING".data(using: String.Encoding.utf8)!

        print("PING")
        self.outputStream?.writeStreamWhenReady(ping)
        sleep(1)

        print("PING")
        self.outputStream?.writeStreamWhenReady(ping)
        sleep(1)

        print("PING")
        self.outputStream?.writeStreamWhenReady(ping)
        sleep(1)

    }

    private func setupStream() {

        guard let host = url.host, let port = url.port else { print("Failed URL parse"); return }

        var readStream: Unmanaged<CFReadStream>?
        var writeStream: Unmanaged<CFWriteStream>?

        CFStreamCreatePairWithSocketToHost(nil, host as CFString!, UInt32(port), &readStream, &writeStream)

        self.inputStream = readStream!.takeRetainedValue() as InputStream
        self.outputStream = writeStream!.takeRetainedValue() as OutputStream

        guard let inStream = self.inputStream, let outStream = self.outputStream else { return }

        inStream.open()
        outStream.open()

        DispatchQueue.global(qos: .utility).sync { [weak self] in

            for stream in [inStream, outStream] {
                stream.delegate = self
                stream.schedule(in: .current, forMode: .defaultRunLoopMode)
            }

            RunLoop.current.run(mode: .defaultRunLoopMode, before: Date.distantFuture)

        }
    }

    func stream(_ aStream: Stream, handle eventCode: Stream.Event) {

        switch aStream {
        case inputStream!:

            switch eventCode {
            case [.hasBytesAvailable]:
                print("PONG")
                break
            default:
                break
            }

        default:
            break
        }

    }

}

1 个答案:

答案 0 :(得分:2)

不要尝试为多线程代码编写单元测试。它会在以后咬你的屁股。

在多个线程上运行的单元测试代码很难的原因是您无法控制线程的执行顺序,也无法控制每个线程的分配时间 - 这是操作系统的决定。

因此,为了确保在另一个线程上提交的代码执行并填充预期的数据,您需要阻止主线程(通常运行单元测试),持续时间足够大以确保另一个线程完成了工作。

现在,棘手的部分是找到那么多的时间。使它太短,你会看到你的单元测试的随机失败,使它太长,你会越来越多地增加单元测试的持续时间。理论上,等待另一个线程完成需要多长时间没有上限,因为这是我们无法控制的(记住,操作系统决定接下来要接收哪个线程以及分配给它的时间)。 / p>

更糟糕的是,当这样的单元测试开始在CI计算机上失败但是它没有在你的机器上失败时,谁应该责怪:CI机器太慢,或者你的代码在某些只发生的条件下行为不端在CI机器上?这种模糊性可能会导致大量时间浪费在试图弄清楚测试代码发生的黑客攻击。

结论:不要尝试为在不同线程上执行部分工作的代码编写单元测试。原因很简单:健壮的单元测试需要控制所测试代码的所有输入,而第二个线程不是它可以控制的东西(除非你模拟线程调度,但这是另一个故事)。

相反,尽可能多地将逻辑推送到单线程方法中,并测试这些方法。最后,大多数错误都是由于不正确的业务逻辑造成的。