Closed 0angelic0 closed 5 years ago
i'm wondering this situation too :). In some situations, we do async network request in background.
Hi guys! I think this could be very nice if it could be incorporated nicely in to the Async framework. I've been doing some work on letting Async block take input parameters + return values. So far I haven't succeeded. Async really is just a DSL for GCD + the chaining syntax.
I suggest you take a look at forbind by my friend @ulrikdamm. It's a future/promises implementation for Swift and looks really "promising" :laughing:
It's no that I don't want to add it to Async, but so far it does quite well doing what is already does + I haven't found a workable solution in Swift yet.
I have some implementation done for the callback feature and can work with Alamofire. But I cannot think how to implement this to Async.
I use classes for self reference (self.next) but Async use structs. Please take a look at DGTAsync.
You can do this right now without adding a pull request at least as a temporary solution. So from what it looks like this project was meant to fire off tasks on background queues but won't wait for an async task (like a network request) to finish before firing off the next block in the chain. This is a common problem when using blocks and long running tasks in general.
WARNING: This may be sloppy and I haven't scoped out what the overhead for doing something like this is.
Also normally you could also use a dispatch_group_t
but that doesn't work out the way we think it would. I won't go into details here just to keep this brief.
This problem can be solved dispatch_semaphore_t
instead.
Our network function makes a fake HTTP request and puts it on its own queue and runs asynchronously.
func longLastingAsyncNetworkTask(url: String, _ completion: () -> Void) {
var queue = dispatch_queue_create("com.networking.queue", DISPATCH_QUEUE_SERIAL)
dispatch_async(queue, { () -> Void in
var waitTime: UInt32 = 0
switch url {
case "http://apple.com":
waitTime = 8
case "http://google.com":
waitTime = 3
case "http://microsoft.com":
waitTime = 5
default:
break
}
sleep(waitTime)
println("[Network] \(url) in \(waitTime) sec(s)")
completion()
})
}
Then of course the meat and potatoes:
Async.userInitiated {
self.longLastingAsyncNetworkTask("http://apple.com") {
// running 1
}
println("Fired Off 1")
}.background {
self.longLastingAsyncNetworkTask("http://google.com") {
// running 2
}
println("Fired Off 2")
}.background {
self.longLastingAsyncNetworkTask("http://microsoft.com") {
// running 3
}
println("Fired Off 3")
}.main {
println("All done.")
}
What you get is:
Fired Off 1
Fired Off 2
Fired Off 3
All done.
[Network] http://google.com in 3 sec(s)
[Network] http://microsoft.com in 5 sec(s)
[Network] http://apple.com in 8 sec(s)
Which is the problem as I understand it. The code is continuing even though the async task inside hasn't finished yet.
Now using the same async method as above with our new meat and potatoes:
var semaphore = dispatch_semaphore_create(0)
Async.userInitiated {
self.longLastingAsyncNetworkTask("http://apple.com") {
dispatch_semaphore_signal(semaphore)
}
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
println("Fired Off 1")
}.background {
self.longLastingAsyncNetworkTask("http://google.com") {
dispatch_semaphore_signal(semaphore)
}
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
println("Fired Off 2")
}.background {
self.longLastingAsyncNetworkTask("http://microsoft.com") {
dispatch_semaphore_signal(semaphore)
}
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
println("Fired Off 3")
}.main {
println("All done.")
}
We get:
[Network] http://apple.com in 8 sec(s)
Fired Off 1
[Network] http://google.com in 3 sec(s)
Fired Off 2
[Network] http://microsoft.com in 5 sec(s)
Fired Off 3
All done.
So what happened? I'll let the Apple Docs explain:
When you create the semaphore, you specify the number of available resources. This value becomes the initial count variable for the semaphore. Each time you wait on the semaphore, the dispatch_semaphore_wait function decrements that count variable by 1. If the resulting value is negative, the function tells the kernel to block your thread. On the other end, the dispatch_semaphore_signal function increments the count variable by 1 to indicate that a resource has been freed up. If there are tasks blocked and waiting for a resource, one of them is subsequently unblocked and allowed to do its work.
After we fired off longLastingAsyncNetworkTask(completion: ()->Void)
we called dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
to decrement our var semaphore
which already had 0 resources to -1.
This stops code execution right before our println("Fired Off X")
and waits for the completion for longLastingAsyncNetworkTask(completion: ()->Void)
to execute dispatch_semaphore_signal(semaphore)
which increments and frees up our semaphore back to 0.
Then our code continues on it's way in order. Also since this is all on a background thread the main thread isn't being blocked until of course you run .main
.
Hope that makes sense.
I have submitted a pull request for adding AsyncGroup to help manage multiple asynchronous operations, including custom ones based or not on GCD.
The PR was not intended to solve the issue on this thread, but this code should work by using a GCD group, even if it only has one operation that we want to wait on:
let group = AsyncGroup()
group.enter()
// async upload
Alamofire.upload(.POST, "http://httpbin.org/post", file: fileURL)
.reponseJSON { (_, _, JSON, _) in
// upload done!!
group.leave()
}
}
group.wait()
Async.background {
// do some stuff S in **background** after upload done :)
S()
}
Hope that helps!
I love the idea of an AsyncGroup. Unfortunately, I'm not able to get it working for my needs. I suspect is has to do with the way I'm calling it.
let group = AsyncGroup()
group.enter()
self.externalInterface.getDataFromNetwork(withCode: code) {
data in
self.retreivedData = data
group.leave()
}
group.wait()
It simply hangs at this point. I suspect it is because my network call is happening in my other class and not in the ExternalInterface class that I'm directly referencing here. By the time the data gets to the code above, it has been returned through 2 different handlers.
@JoshHrach I haven't actually used AsyncGroup myself :)
Please open a separate issues with as much code and explanation as possible. Hopefully someone will be able to help you out!
@duemunk I'll do that tomorrow. Thanks.
@duemunk I never did open another issue. But I did get it resolved. I ended up adding support for dispatch_group_notify() and made a pull request with it. It was the only thing missing from AsyncGroup (mentioned earlier in this issue) that I needed for our project.
Project is in maintenance mode, so I'm closing this issue. Thanks for everyone's work!
It can be very useful if Async can use with async operations.
Right now, I think Async can use only with sync operations.
But if I use some async operations such as Alamofire to upload files. Now the chain is just not wait for an upload to be finished.
An idea for accomplish this is something like passing a
done
block and waiting fordone()
to be called to continue to the next Async chain. I saw Quick/Nimble use this forwaitUntil
operation here.So, here is a use case proposal.
The Async is now very smart and every operations (.background, .main, .utility, etc..) can take a parameterless block for sync operations and a "one parameter block" (that parameter is another block expose as parameter name
done
) for async operations.Any thought or possibility?