When using the toolchain from April 10 the new concurrency api is annotated with
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
So it is not possible to try it out - is there a reason for that (and a workaround)?
When using the toolchain from April 10 the new concurrency api is annotated with
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
So it is not possible to try it out - is there a reason for that (and a workaround)?
I double this. Today's toolchain broke everything. It wasn't production code, of course, but I was just playing around with async stuff. Wrapping everything with available
isn't really an option, tbh.
You can try it out if you annotate your own code with
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
And use
if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) {
detach {
runYourAsyncCode()
}
}
I guess this is here because we'll only be able to run new concurrency code on macOS, iOS versions which include the concurrency libraries. So only available iOS 15 or later, macOS future version and those version numbers have not been set yet.
We can now use async/await
without the flag -Xfrontend -enable-experimental-concurrency
, however we can't call the function.
So if SE-0304 Structured concurrency is not implemented on Swift 5.5, we can't use async/await
, though they are implemented.
Like I said, this is unacceptable — code size and complexity would become absurd. They could at least enhance -enable-experimental-concurrency
flag so that it would bypass those @available
checks.
I think you can pass -Xfrontend -disable-availability-checking
to the compiler to disable availability checking, which should allow you to use the toolchain normally.
Looks like using both -enable-experimental-concurrency
and -disable-availability-checking
get the async APIs back.
Anyone know what the replacement for @asyncHandler
would be?
Probably using detach { ... }
in the body. My understanding is that @asyncHandler
is just sugar over that. You can still use @asyncHandler
for the time being if you pass -enable-experimental-async-handler
but it is meant to be removed, though I don't know when.
Thanks for the tip. However, couldn't make it work in SPM:
swiftSettings: [
.unsafeFlags([
"-Xfrontend", "-enable-experimental-concurrency",
"-Xfrontend", "-disable-availability-checking",
])
]
Didn't work at all for some reason, still having availability errors
It works in Xcode directly.
This doesn't seem to work for things like Obj-C delegate methods, so there's probably more that is needed.
I wonder why they want to remove @asyncHandler
– I like how it discourages us from using detach(_:)
throughout our code, meaning that people are more likely to stick to structured concurrency instead of starting new top-level tasks everywhere.
Also, @Douglas_Gregor said a while back that @asyncHandler
is "more optimizable syntactic sugar" for detach(_:)
. So really it seemed like a win-win for me.
There are a lot of basic questions about @asyncHandler
that we're not confident about right now:
Sure. In the meantime, what's the preferred solution for implementing the URLSessionDelegate
with an actor? Or is that broken for the time being?
I think we understand that URLSession
will need some rethinking to fit more cleanly with async/await. I don't think it's impossible to make it work with structured concurrency instead of detached tasks, though, if you do some fairly subtle things with continuations. If you have a short sample, I can show you what I mean.
Sure, I had the following example working previously. However, without asyncHandler
, there doesn't seem to be a way to get it to compile, as I get the error Actor-isolated instance method 'urlSession(_:task:didCompleteWithError:)' cannot be @objc
, even when using detach
internally.
actor Session {
private(set) lazy var session = URLSession(configuration: .default, delegate: sessionDelegate, delegateQueue: nil)
private(set) lazy var sessionDelegate = SessionDelegate(self)
private(set) var requests: [URLSessionTask: DataRequest] = [:]
func request() async throws -> DataRequest {
let request = DataRequest()
let task = await request.task(using: session)
requests[task] = request
task.resume()
return request
}
func request(for task: URLSessionTask) -> DataRequest? {
requests[task]
}
}
actor SessionDelegate: NSObject {
private weak var owner: Session?
init(_ owner: Session) {
self.owner = owner
}
}
actor DataRequest {
private(set) var data = Data()
private(set) var error: Error?
private var completion: ((Data, Error?) -> Void)?
func task(using session: URLSession) -> URLSessionTask {
session.dataTask(with: URLRequest(url: URL(string: "https://httpbin.org/get")!))
}
func didComplete(_ error: Error?) {
self.error = error
print("didComplete")
completion?(data, self.error)
}
func didReceive(_ data: Data) {
self.data.append(data)
}
func awaitCompletion() async throws -> Data {
try await withUnsafeThrowingContinuation { continuation in
self.completion = { data, error in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: data)
}
}
}
}
}
extension URLSession: UnsafeSendable {}
extension SessionDelegate: URLSessionTaskDelegate {
// func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
// await self.owner?.request(for: task)?.didComplete(error)
// }
}
extension SessionDelegate: URLSessionDataDelegate {
// func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
// await owner?.request(for: dataTask)?.didReceive(data)
// }
}
I'm also having a lot of trouble getting the initializers I want to work, but that's another issue.
Hmm. Your Session
is conceptually an actor, but I'm not sure you can reasonably use a language-supported actor for this with the current implementation because of the complex interaction with URLSession's use of queues. Custom executors should make this reasonable to model directly as an actor, although I think we might need some way way to tell the system that it can assume you're running on a particular executor already. In the meantime, you can use a queue internally and present an async
interface externally, which isn't very satisfying technically but does do the job of making this fit into a program that's broadly using async/await. I think your example isn't doing anything that you couldn't have done with the completion-handler interface instead of the more general delegate, though, so maybe there's a gap with the real-world example here.
Right. This example is just an experiment towards the beginning of rebuilding Alamofire using the new concurrency model. The current version already uses serial queues internally to manage async state updates and locks to manage synchronous functions and mutable state. I'm trying to gain insight into how the concurrency features work by applying them to the concurrency domain I've already built. I also have an experiment offering async
handlers instead of completion handlers, this is just the next step.
It's funny you should mention the URLSession
queuing, as I brought that example up several times in previous pitches. Eventually I got an answer that the queue shouldn't really matter as there may simply be a performance impact due to queue hopping between the URLSession
's delegateQueue
and the various executors. Are there considerations beyond that?
Frankly, while my current model does use the same underlying DispatchQueue
as both my Session
's shared rootQueue
and the URLSession
's delegateQueue
, as that avoids a whole lot of async
calls between the two, it does seem like a better idea to allow the URLSession
's queue to operate independently so throughput isn't affected by work being performed by the requests. Replacing my existing serial DispatchQueue
with the built in concurrency seems like a good idea, no?
So I'd still be very interested in a model that allows me make an actor a URLSessionDelegate
, as that seems like something that needs to be possible no matter what. I took the compiler's advice and marked the delegate methods nonisolated
and used detach
to call my existing await
calls. Is that roughly equivalent to what @asyncHandler
did?
I think that might be exactly what the old @asyncHandler
did.
The existing URLSessionDelegate
design is essentially an "actor protocol" — all the requirements are invoked on a consistent executor, namely, the operation queue you construct the session with — except that there's really no way for the compiler to know and automate that. Probably the most direct port would be to make an initializer of URLSession
that takes an instance of an actor protocol corresponding to everything in the URLSessionDelegate
hierarchy, and then make an ObjC adapter which sits between them. The big problem with that approach, of course, is that URLSessionDelegate
and its derived protocols trade heavily on optional requirements, which aren't supported in a non-@objc
protocol, which an actor protocol would have to be.
How? I couldn't even find build settings anymore, I assume it's only available for .xcodeproj project, not SPM one.