Task safe way to write a file asynchronously

Am I correct in thinking that blocking I/O functions shouldn't be
called from within a Task?

That depends on the constraints of your I/O. If you’re writing small amounts of data to a file on a critical volume — on the Mac this would be the root file system or the file system containing the user’s home directory — then synchronous file I/O should be fine. It’ll consume a Swift concurrency worker thread for the duration, but there’s a reasonable bound on that duration [1].

Beyond that, things get complex. On Apple platforms the file system code within the kernel is fundamentally synchronous. The thread that calls into the kernel does all the work, blocking if it’s necessary to wait for I/O. This affects all layers of the kernel, from the system call layer, to VFS, to APFS, to UBC. Asynchronous operations only come back in play once you get down to I/O Kit.

So, there’s no general-purpose async file I/O API you can use to underpin a Swift async file I/O library. What you do about that depends on your specific requirements. In some cases you might find a good option. The case that immediately springs to mind is where you’re transferring large amount of data. Dispatch I/O is really good at this. In other cases, you’re kinda on your own. For example, if you’re doing something metadata heavy, like traversing a large directory hierarchy, it’s probably best to spin up your own thread for that work.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

[1] There’s no actual bound — if you hit a disk error it could take many seconds to succeed or fail, and if the user has their home directory on a network file system it could take many minutes to complete, or indeed never — but, if a critical volume is misbehaving in this way, the user has bigger problems.

13 Likes