Swift 函数超时

Timeout for Swift functions

提问人:damdamo 提问时间:10/12/2023 更新时间:10/12/2023 访问量:54

问:

我写完了一个 Swift 库,我需要在一些示例上测试它的性能。

我正在 Swift 中创建一个脚本,将一些数据放入文件中,使用我的库执行它们,然后返回执行时间等结果。

在从这些文件中提取的这些不同示例的列表中,其中一些可能需要数小时才能执行,而另一些则只需要几秒钟。我不期望所有示例都有答案,我只想要那些在不到“x”秒内完成的结果。出于这个原因,如果函数的调用时间超过“x”秒,我想引入超时。它应该停止当前执行并转到下一个执行。

我寻找解决方案,其中一些使用 async/await 或 dispatchQueue,但没有一个对我有用。以下是我尝试过的解决方案之一:

func evalWithTimer(key: String, somethingToExecute: SomethingToExecute) -> SVS? {

  var res: SVS? = nil
  // Create a semaphore
  let semaphore = DispatchSemaphore(value: 0)

  // Define the time limit in seconds
  let timeLimit: DispatchTime = .now() + 1 // 1 seconds

  // Define the task you want to run (replace this with your actual task)
  let task = {
    res = somethingToExecute.eval()
    semaphore.signal() // Signal that the task is done
  }

  // Execute the task asynchronously
  DispatchQueue.global().async(execute: task)

  // Wait for the task to complete or time out
  let result = semaphore.wait(timeout: timeLimit)

  if result == .timedOut {
    // Task did not complete within the time limit, you can cancel it if needed
    print("Task evaluation timed out and was canceled")
    return res
  } else {
    // Task completed within the time limit
    print("Task evaluation completed within the time limit")
    return res
  }

}

我的函数 evalWithTimer 是针对 somethingToExecute 中的不同值调用的。当我调用它时,它可能需要 1 秒以上,如果这是真的,它应该停止。在此当前版本中,它不起作用,因为未正确取消任务。至少这是我的假设。即使在超时为 true 时添加也不会更改任何内容。eval()eval()task.cancel()

我发现的唯一解决方案添加了异步机制,这是可以理解的。我正在寻找这个问题的解决方案,无论是否基于上面的代码。

请注意,这不是异步的。somethingToExecute.eval()

Swift 异步 async-await 超时

评论

0赞 Sweeper 10/12/2023
停止事情应该是一种合作的努力。你能改变实现方式吗?eval
0赞 damdamo 10/12/2023
是的,我可以,但这意味着更改初始库。我该怎么办?编辑:该函数对库有重大影响,并对其进行了许多测试。就我而言,这仅用于我自己测试,但它不会为用户改变任何内容。eval()
0赞 Sweeper 10/12/2023
实际上,如果您在测试环境中执行此操作,那么等待呢?这些方法允许您指定超时。例如,参见 developer.apple.com/documentation/xctest/xctwaiter/...XCTestExpectation
0赞 damdamo 10/12/2023
当我在最初的帖子中使用“测试”这个词时,我并不是指对库的典型测试。该库已经用 Swift 的测试框架进行了测试,一切正常。现在,我正在进行一些基准测试。这个新代码是 Swift 可执行文件,我不再使用 Swift 的测试框架。也许我应该重新考虑我想如何收集我的数据。

答:

1赞 Sweeper 10/12/2023 #1

如果您正在进行“测试”,我建议使用 XCTest。您可以等待超时。XCTestExpectation

func testExample() {
    let expect = expectation(description: "Eval completed in 1 second")
    DispatchQueue.global().async {
        somethingToExecute.eval()
        expect.fulfill()
    }
    wait(for: [expect], timeout: 1)
}

当超过超时时,测试进程将被终止。


不过,总的来说,停止事情应该是一种合作努力。 应该检查自己是否被取消,如果是,停止它正在做的任何事情。eval

例如,使用 Swift Concurrency,可以在其执行过程中的适当时间点检查 Task.isCancelled,或者如果 ,则可以。evalthrowstry Task.checkCancellation()

假设是一个长时间运行的循环,你可以在每次迭代中进行检查:eval

while something {
    if Task.isCancelled { return nil /* or however you'd like to stop it*/ }
    // or in a throwing function:
    // try Task.checkCancellation()

    // do work
}

然后,您可以执行两个任务,一个是 运行 ,另一个测量 1 秒,然后取消第一个任务。eval

func evalWithTimer(key: String, somethingToExecute: SomethingToExecute) async throws -> SVS? {
    let evalTask = Task {
        somethingToExecute.eval()
        // if eval is throwing,
        // try somethingToExecute.eval()
    }
        
    let timeoutTask = Task {
        try await Task.sleep(for: .seconds(1))
        evalTask.cancel()
        // here you know the timeout is exceeded
    }
        
    // if eval is throwing, "try" await, a CancellationError would be thrown if the timeout exceeds
    let result = await evalTask.value
    timeoutTask.cancel()
    return result
}