DispatchAsync is a temporary experimental repository aimed at implementing missing Dispatch support in the SwiftWasm toolchain. Currently, SwiftWasm doesn't include Dispatch. But, SwiftWasm does support Swift Concurrency. DispatchAsync implements a number of common Dispatch API's using Swift Concurrency under the hood.
Dispatch Async does not provide blocking API's such as DispatchQueue.sync
, primarily due to the intentional lack of blocking
API's in Swift Concurrency.
DispatchAsync is not meant for consumption abroad directly as a new Swift Module. Rather, the intention is to provide eventual integration as a drop-in replacement for Dispatch when compiling to Wasm.
There are a few paths to adoption into the Swift toolchain
- DispatchAsync can be emplaced inside the libDispatch repository, and compiled into the toolchain only for wasm targets.
- DispatchAsync can be consumed in place of libDispatch when building the Swift toolchain.
Ideally, with either approach, this repository would transfer ownership to the swiftlang organization.
In the interim, to move wasm support forward, portions of DispatchAsync may be inlined (copy-pasted)
into various libraries to enable wasm support. DispatchAsync is designed for this purpose, and has
special #if
handling to ensure that existing temporary usages will be elided without breakage
the moment SwiftWasm adds support for Dispatch
into the toolchain.
The current implementation of DispatchSemaphore
has some limitations. Blocking threads goes against the design goals of Swift Concurrency.
The wait
function on DispatchSemaphore
goes against this goal. Furthermore, most wasm targets run on a single thread from the web
browser, so any time the wait
function ends up blocking the calling thread, it would almost certainly freeze the single-threaded wasm
executable.
To navigate these issues, there are some limitations:
- For wasm compilation targets,
DispatchSemaphore
assumes single-threaded execution, and lacks various safeguards that would otherwise be needed for multi-threaded execution. This makes the implementation much easier. - For wasm targets, calls to
signal
andwait
must be balanced. An assertion triggers ifwait
is called more times thansignal
. - DispatchSemaphore is deprecated for wasm targets, and AsyncSemaphore is encouraged as the replacement.
- For non-wasm targets, DispatchSemaphore is simply a typealias for
AsyncSemaphore
, and provides only a non-blocking asyncwait
function. This reduces potential issues that can arise from wait being a thread-blocking function.
If you've scrolled this far, you probably saw the warning. But just to make sure…
⚠️ WARNING - This is an 🧪experimental🧪 repository and should not be adopted at large at the present time.
PassiveLogic is actively working to mainstream this into the SwiftWasm toolchain. But if you can't wait, here are some tips.
Use #if os(WASI) && !canImport(Dispatch)
to elide usages outside of WASI platforms:
#if os(WASI) && !canImport(Dispatch)
import DispatchAsync
#else
import Dispatch
#endif
// Use Dispatch API's the same way you normal would.
2. If you really want to use DispatchAsync as a pure Swift Dispatch alternative for non-wasm targets
Stop. Are you sure? If you do this, you'll need to be careful with all import Dispatch
, import Foundation
, and many other issues.
- Add the dependency to your package:
let package = Package(
name: "MyPackage",
products: [
.library(
name: "MyPackage",
targets: [
"MyPackage"
]
),
],
dependencies: [
.package(
url: "https://github.com/PassiveLogic/swift-dispatch-async.git",
from: "0.0.1"
),
],
targets: [
.target(
name: "MyPackage"
dependencies: [
.product(name: "DispatchAsync", package: "swift-dispatch-async", condition: .when(platforms: [.wasi])),
]
),
]
)
- Import and use DispatchAsync in place of Dispatch like this:
#if os(WASI) && !canImport(Dispatch)
import DispatchAsync
#else
// Non-WASI platforms have to explicitly bring in DispatchAsync
// by using `@_spi`.
@_spi(DispatchAsync) import DispatchAsync
#endif
// Not allowed, brings in Dispatch, aka "the real GCD":
// import Dispatch
// Also not allowed, brings in Dispatch
// import Foundation
// You'll need to use scoped Foundation imports:
import struct Foundation.URL // Ok. Doesn't bring in Dispatch
// If you ignore the above notes, but do the following, be prepared for namespace
// collisions between the toolchain's Dispatch and DispatchAsync:
private typealias DispatchQueue = DispatchAsync.DispatchQueue // Ok as long as Dispatch isn't imported
// Ok. If you followed everything above, you can now do the following, using pure Swift
// under the hood! 🎉
DispatchQueue.main.async {
// Run your code here…
}
This project is distributed by PassiveLogic under the Apache-2.0 license. See LICENSE for full terms of use.