将阻止功能集成到Swift Async中

发布于 2025-02-12 18:41:20 字数 355 浏览 0 评论 0原文

我知道异步的Swift 任务不应该阻止(异步工作线程必须始终使进度取得进展)。如果我有其他100%的异步Swift应用程序,但需要引入一些阻止任务,那么做到这一点的正确方法是什么不会阻止任何Swift Async线程池工人?

我假设需要一个新的专用线程在异步线程池外面需要一个假设,如果该假设是正确的,那么线程函数的线程安全方法是什么是等待该线程完成的螺纹函数?我可以使用with checkedContinuation启动线程,复制continuation将其搬运到该线程中,然后调用continuation.resume.resume.resume在该线程完成?

I understand that an asynchronous Swift Task is not supposed to block (async worker threads must always make forward progress). If I have an otherwise 100% async Swift application but need to introduce some blocking tasks, what is the correct way to do this that will not block any of the swift async thread pool workers?

I'm assuming a new dedicated thread outside of the async thread pool is required, if that assumption is correct what is the thread safe way for an async function to await for that thread to complete? Can I use the body of withCheckedContinuation to launch a thread, copy the continuation handle into that thread and call continuation.resume from that thread when it completes?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

抚你发端 2025-02-19 18:41:20

我知道,异步的Swift任务不应该阻止(异步工作线程必须始终取得前进的进度)。

这是正确的。 Swift并发系统的基石是任务必须始终在前进。

我可以使用with checkedContinuation启动线程,将持续句柄复制到该线程中,然后调用连续性。

是的,这也是正确的,正是连续性的目的:

checkedContinuation
同步和异步代码之间接口的机制,记录正确性违规。

延续的目的是使您可以将阻止同步操作拟合到async世界中。当您致电

[s] uspend当前任务,然后通过检查当前任务的检查延续来调用给定的封闭。

该任务无限期地暂停,直到您恢复它为止,这允许其他任务在此期间取得进展。您获得的延续值是一个线程安全接口,以表明您的阻塞操作已经完成,并且原始任务应在下一个可用的机会中恢复。延续还为 sendable ,这表明您可以在线程之间安全地传递它。任何线程都可以恢复任务,因此您甚至不一定需要回到同一线程上的延续。

来自

func operation() async -> OperationResult {
  // Suspend the current task, and pass its continuation into a closure
  // that executes immediately
  return await withUnsafeContinuation { continuation in
    // Invoke the synchronous callback-based API...
    beginOperation(completion: { result in
      // ...and resume the continuation when the callback is invoked
      continuation.resume(returning: result)
    }) 
  }
}

请注意,这仅是真正阻止的任务所必需的,并且在某些方面之前无法取得进一步的进步他们依靠完成。这与执行主动计算的任务不同,这些任务恰好需要很长时间,因为这些任务至少是在积极进步的任务。 (但是在聊天中,您澄清了您的用例是前者。)

I understand that an asyncronous Swift Task is not supposed to block (async worker threads must always make forward progress).

This is correct. The cornerstone of the Swift concurrency system is that tasks must always be making forward progress.

Can I use the body of withCheckedContinuation to launch a thread, copy the continuation handle into that thread and call continuation.resume from that thread when it completes?

Yes, this is also correct, and is exactly the purpose of continuations:

CheckedContinuation
A mechanism to interface between synchronous and asynchronous code, logging correctness violations.

The purpose of a continuation is to allow you to fit blocking synchronous operations into the async world. When you call withCheckedContinuation, it

[s]uspends the current task, then calls the given closure with a checked continuation for the current task.

The task is suspended indefinitely until you resume it, which allows other tasks to make progress in the meantime. The continuation value you get is a thread-safe interface to indicate that your blocking operation is done, and that the original task should resume at the next available opportunity. The continuation is also Sendable, which indicates that you can safely pass it between threads. Any thread is allowed to resume the task, so you don't even necessarily need to call back to the continuation on the same thread.

An example usage from SE-0300: Continuations for interfacing async tasks with synchronous code:

func operation() async -> OperationResult {
  // Suspend the current task, and pass its continuation into a closure
  // that executes immediately
  return await withUnsafeContinuation { continuation in
    // Invoke the synchronous callback-based API...
    beginOperation(completion: { result in
      // ...and resume the continuation when the callback is invoked
      continuation.resume(returning: result)
    }) 
  }
}

Note that this is only necessary for tasks which are truly blocking, and cannot make further progress until something they depend on is done. This is different from tasks which perform active computation which just happen to take a long time, as those are tasks which are at least making active progress. (But in chat, you clarify that your use-case is the former.)

水水月牙 2025-02-19 18:41:20

你说:

我假设需要在异步线池外面的新专用线程……

我不会得出结论。这完全取决于此“阻止任务”的性质。

  1. 如果仅仅是一些缓慢的同步任务(例如,CPU密集型任务),则可以留在Swift并发系统中,并在独立任务或参与者中执行此同步任务。但是,如果这样做,则必须定期 task.yield.yield() 在此任务中,以确保您履行Swift并发合同以“确保前进进度”。

  2. 如果是一些阻止API可以在该线程上等待/睡觉,那么,正如Itai建议的那样,我们将其包裹在延续中,理想情况下,用非阻止api代替该API。如果用异步演绎替换阻止API是不切实际的,那么,是的,您可以为此旋转自己的线程,有效地使其成为异步任务,然后将其包裹在延续中。


但是,在长期运行,阻止,计算的背景下,如果一个人不能定期yart se-0296-异步/等待说一个应该在“单独的上下文”中运行它(重点添加):

由于潜在的悬架点只能出现在异步函数中明确标记的点处,因此长度计算仍然可以阻止线程。当调用仅执行大量工作的同步函数时,或者遇到直接以异步函数写入的特别激烈的计算循环时,可能会发生这种情况。无论哪种情况,这些计算运行时都无法交织代码,这通常是正确性的正确选择,但也可能成为可扩展性问题。 需要执行强度计算的异步程序通常应在单独的上下文中运行。如果这是不可行的,则将有库设施人为地暂停并允许其他操作进行交织。

>

该提案的长期计算没有能力,这表明一个“在单独的上下文中运行”。例如,在WWDC 2022的 ,Apple明确建议将阻止代码移出Swift并发系统:

一定要避免阻止任务中的呼叫。 …如果您有需要执行这些操作的代码,请在并发线程池之外移动该代码 - 例如,通过在调度值上运行它 - 并使用连续性将其桥接到并发世界。

因此,例如,您可以执行以下操作(假设valuesoddable):

func foo() async -> Value {
    await withCheckedContinuation { continuation in
        DispatchQueue.global(qos: .utility).async {
            let value = slowAndSynchronous()
            continuation.resume(returning: value)
        }
    }
}

另外,如今,您可以使用Actor从Swift并发合作线程池中获取它用

actor Foo {
    private let queue = DispatchSerialQueue(label: Bundle.main.bundleIdentifier! + ".Foo", qos: .utility)

    nonisolated var unownedExecutor: UnownedSerialExecutor {
        queue.asUnownedSerialExecutor()
    }

    func foo() -> Value {
        // you can confirm that you are not in the cooperative thread pool
        //
        // dispatchPrecondition(condition: .onQueue(queue))

        return slowAndSynchronous()
    }
}

href = “ 如果同步函数无法定期产生,允许交错,请通过其中一个将此代码移到合作线程池外上面概述的机制。

请注意,Swift并发无法推理其范围之外的其他线程,并将继续充分利用合作的线程池,从而导致CPU的潜在过度使用(合作线程池旨在解决问题之一)。这是次优的,因此需要一些护理,但是如果您使用迅速并发与阻止或计算密集型库一起设计的没有合作的线程池的设计,则可能是必要的邪恶。

You said:

I'm assuming a new dedicated thread outside of the async thread pool is required …

I would not jump to that conclusion. It depends entirely upon the nature of this “blocking task”.

  1. If it simply is some slow, synchronous task (e.g. a CPU-intensive task), then you can stay within the Swift concurrency system and perform this synchronous task within a detached task or an actor. If you do this, though, you must periodically Task.yield() within this task in order to ensure that you fulfill the Swift concurrency contract to “ensure forward progress”.

  2. If it is some blocking API that will wait/sleep on that thread, then, as Itai suggested, we would wrap it in a continuation, ideally, replacing that blocking API with a non-blocking one. If it is not practical to replace the blocking API with an asynchronous rendition, then, yes, you could spin up your own thread for that, effectively making it an asynchronous task, and then wrapping that within a continuation.


But, in the context of long-running, blocking, computations, if one cannot periodically yield, SE-0296 - Async/await says that one should run it in a “separate context” (emphasis added):

Because potential suspension points can only appear at points explicitly marked within an asynchronous function, long computations can still block threads. This might happen when calling a synchronous function that just does a lot of work, or when encountering a particularly intense computational loop written directly in an asynchronous function. In either case, the thread cannot interleave code while these computations are running, which is usually the right choice for correctness, but can also become a scalability problem. Asynchronous programs that need to do intense computation should generally run it in a separate context. When that’s not feasible, there will be library facilities to artificially suspend and allow other operations to be interleaved.

Re long computations that do not have that capability, the proposal suggests that one “run it in a separate context”. For example, in WWDC 2022’s Visualize and optimize Swift concurrency, Apple explicitly advises moving the blocking code out of the Swift concurrency system:

Be sure to avoid blocking calls in tasks. … If you have code that needs to do these things, move that code outside of the concurrency thread pool – for example, by running it on a DispatchQueue – and bridge it to the concurrency world using continuations.

So, for example, you can do the following (assuming if Value is Sendable):

func foo() async -> Value {
    await withCheckedContinuation { continuation in
        DispatchQueue.global(qos: .utility).async {
            let value = slowAndSynchronous()
            continuation.resume(returning: value)
        }
    }
}

Alternatively, nowadays you can get it out of the Swift concurrency cooperative thread pool using an actor with a custom executor:

actor Foo {
    private let queue = DispatchSerialQueue(label: Bundle.main.bundleIdentifier! + ".Foo", qos: .utility)

    nonisolated var unownedExecutor: UnownedSerialExecutor {
        queue.asUnownedSerialExecutor()
    }

    func foo() -> Value {
        // you can confirm that you are not in the cooperative thread pool
        //
        // dispatchPrecondition(condition: .onQueue(queue))

        return slowAndSynchronous()
    }
}

Bottom line, if the synchronous function cannot periodic yield, to allow interleaving, move this code outside of the cooperative thread pool via one of the mechanisms outlined above.

Just be aware that Swift concurrency cannot reason about other threads outside of its scope and will continue to fully avail itself of the cooperative thread pool leading to a potential over-commit of the CPU (one of the problems the cooperative thread pool was designed to solve). This is suboptimal, so some care is needed, but it may be a necessary evil if you are using Swift concurrency in conjunction with blocking or computationally intensive library designed without the cooperative thread pool in mind.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文