【问题标题】:Swift Cancel DispatchQueue ProcessSwift 取消 DispatchQueue 进程
【发布时间】:2019-06-11 20:06:28
【问题描述】:

我有一个使用 DispatchQueue 等待回复的 UDP 方法,该方法使用以下代码:

DispatchQueue.global(qos: .userInitiated).async {
    let server:UDPServer=UDPServer(address:"0.0.0.0", port:5005)
    let (data,_,_) = server.recv(1024)
    DispatchQueue.main.async {
       ...
    }
}

这非常有效,并启动了一个等待我的数据进入的过程。让我彻夜难眠的原因是如果我们从未得到回复会发生什么? server.recv 永远不会返回,所以我看不到该过程将如何结束?有没有办法给它预定的运行时间?

【问题讨论】:

  • 查看这篇文章中的讨论:stackoverflow.com/questions/38105105/…
  • 它没有我知道的内置超时功能。我唯一能想到的是使用cancel() 并从回调内部检查isCancelled 属性。但是,如果 recv 被阻塞,这将不起作用。
  • UDPServer 是如何实现的? recv 长什么样子?没有看到实际的阻塞代码很难给你建议。
  • 我正在使用 SwiftSockets。
  • 看了一下SwiftSockets,看起来你想要的是不可能的,因为SwiftSockets不提供stackoverflow.com/questions/13547721/udp-socket-set-timeout中指定的功能

标签: swift dispatch-queue


【解决方案1】:

没有办法从外部阻止或“杀死”DispatchWorkItemNSOperation。有一个cancel() 方法,但它只是将项目或操作的isCancelled 属性设置为true。这不会停止项目本身的执行。 Ans 因为recv 是阻塞的,所以在执行期间无法检查isCancelled 标志。这意味着 Vadian 发布的答案很遗憾不会做任何事情。

根据NSOperation.cancel上的Apple docs

此方法不会强制您的操作代码停止。

The same 对应 NSOperationQueue.cancelAllOperations

取消操作不会自动将它们从队列中删除或停止当前正在执行的操作。

您可能认为可以直接使用原始NSThread。然而,同样的原则也适用于他。您不能确定性地从外部杀死线程。

可能的解决方案:超时

我能想到的最佳解决方案是使用套接字的超时功能。我不知道UDPServer 来自哪里,但也许它有一个内置的超时。

可能的解决方案:穷人的超时(发送数据包到本地主机)

您可以尝试的另一个选项是在经过一定时间后向自己发送一些 UDP 数据包。这样,recv 将收到一些数据,并继续执行。这可能被用作“穷人的超时”。

【讨论】:

    【解决方案2】:

    不应该是UDPServer不会返回的任何情况。 UDPServer 应该等待多长时间应该有超时限制。例如考虑以下情况:

     DispatchQueue.global(qos: .background).async {
     let server = UDPServer(address:"0.0.0.0", port:5005)
        switch server.recv(1024) {
            case .success:
                print("Server received message from client.")
            case .failure(let error):
                print("Server failed to received message from client: \(error)")
            }
    
            server.close()
            DispatchQueue.main.async {
                ...
            }
        }
    

    【讨论】:

    • 但是如何设置超时时间?
    • 我正在使用 SwiftSockets 但在 UDP 端看不到超时?
    【解决方案3】:

    添加一个在特定超时间隔后触发的计时器

    为超时间隔声明一个常量,为计时器声明一个属性

    private let timeoutSeconds = 30
    private var timer : DispatchSourceTimer?
    

    并编写两个函数来启动和停止计时器。

    fileprivate func startDispatchTimer()
    {
        let interval : DispatchTime = .now() + .seconds(timeoutSeconds)
        if timer == nil {
            timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
            timer!.schedule(deadline:interval)
            timer!.setEventHandler {
                // do something when the timer fires
                self.timer = nil
            }
            timer!.resume()
        }
    }
    
    fileprivate func stopDispatchTimer()
    {
        timer?.cancel()
        timer = nil
    }
    

    在初始化服务器实例后启动计时器并在成功时停止它。失败时在 setEventHandler 闭包中添加代码以处理超时,例如解除分配服务器实例。

    【讨论】:

    • 但是您将如何实际取消当前在后台运行的 DispatchWorkItem?您不能直接停止 DispatchWorkItem,并且由于 server.recv 永远不会返回,因此您也无法检查它是否被取消。
    • 仍然,您无法从外部停止操作。您可以取消它,但这只会将isCancelled 标志设置为true。定期检查该标志仍然是操作本身的工作,如果它设置则停止执行。由于recv 处于阻塞状态,因此无法在执行期间检查isCancelled 标志。
    • 您可以致电cancelAllOperations(),但这不会停止任何正在运行的操作。来自文档:“取消操作不会自动将它们从队列中删除或停止那些当前正在执行的操作。对于已经执行的操作,操作对象本身必须检查取消并停止它正在执行的操作以便它可以移动到完成状态。” (developer.apple.com/documentation/foundation/operationqueue/…)。因此,如果在 recv 方法阻塞时触发计时器,则无法停止执行。
    • @l'L'l:是的,我知道您可以通过DispatchWorkItem 致电cancel()。但是,这只是将isCancelled 属性设置为true。停止执行仍然是工作项本身的工作。从这个意义上说,您不能停止或“杀死”一个工作项。您只能要求它自行停止。这在 OP 的情况下不起作用。
    • @l'L'l:通常这会起作用。然而,在这种情况下,recv 永远不会返回,使得 while 循环无用。
    猜你喜欢
    • 2021-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-18
    • 1970-01-01
    • 1970-01-01
    • 2020-09-04
    • 2018-05-09
    相关资源
    最近更新 更多