将阻止功能集成到Swift Async中
我知道异步的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 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这是正确的。 Swift并发系统的基石是任务必须始终在前进。
是的,这也是正确的,正是连续性的目的:
延续的目的是使您可以将阻止同步操作拟合到
async
世界中。当您致电该任务无限期地暂停,直到您恢复它为止,这允许其他任务在此期间取得进展。您获得的延续值是一个线程安全接口,以表明您的阻塞操作已经完成,并且原始任务应在下一个可用的机会中恢复。延续还为 sendable ,这表明您可以在线程之间安全地传递它。任何线程都可以恢复任务,因此您甚至不一定需要回到同一线程上的延续。
来自:
请注意,这仅是真正阻止的任务所必需的,并且在某些方面之前无法取得进一步的进步他们依靠完成。这与执行主动计算的任务不同,这些任务恰好需要很长时间,因为这些任务至少是在积极进步的任务。 (但是在聊天中,您澄清了您的用例是前者。)
This is correct. The cornerstone of the Swift concurrency system is that tasks must always be making forward progress.
Yes, this is also correct, and is exactly the purpose of continuations:
The purpose of a continuation is to allow you to fit blocking synchronous operations into the
async
world. When you callwithCheckedContinuation
, itThe 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:
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.)
你说:
我不会得出结论。这完全取决于此“阻止任务”的性质。
如果仅仅是一些缓慢的同步任务(例如,CPU密集型任务),则可以留在Swift并发系统中,并在独立任务或参与者中执行此同步任务。但是,如果这样做,则必须定期
task.yield.yield()
在此任务中,以确保您履行Swift并发合同以“确保前进进度”。如果是一些阻止API可以在该线程上等待/睡觉,那么,正如Itai建议的那样,我们将其包裹在延续中,理想情况下,用非阻止api代替该API。如果用异步演绎替换阻止API是不切实际的,那么,是的,您可以为此旋转自己的线程,有效地使其成为异步任务,然后将其包裹在延续中。
但是,在长期运行,阻止,计算的背景下,如果一个人不能定期
yart
, se-0296-异步/等待说一个应该在“单独的上下文”中运行它(重点添加):该提案的长期计算没有能力,这表明一个“在单独的上下文中运行”。例如,在WWDC 2022的 ,Apple明确建议将阻止代码移出Swift并发系统:
因此,例如,您可以执行以下操作(假设
value
是soddable
):另外,如今,您可以使用Actor从Swift并发合作线程池中获取它用
href = “ 如果同步函数无法定期产生,允许交错,请通过其中一个将此代码移到合作线程池外上面概述的机制。
请注意,Swift并发无法推理其范围之外的其他线程,并将继续充分利用合作的线程池,从而导致CPU的潜在过度使用(合作线程池旨在解决问题之一)。这是次优的,因此需要一些护理,但是如果您使用迅速并发与阻止或计算密集型库一起设计的没有合作的线程池的设计,则可能是必要的邪恶。
You said:
I would not jump to that conclusion. It depends entirely upon the nature of this “blocking task”.
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”.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):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:
So, for example, you can do the following (assuming if
Value
isSendable
):Alternatively, nowadays you can get it out of the Swift concurrency cooperative thread pool using an actor with a custom executor:
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.