Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async for non-GCD async operations #31

Closed
0angelic0 opened this issue Apr 24, 2015 · 10 comments
Closed

Async for non-GCD async operations #31

0angelic0 opened this issue Apr 24, 2015 · 10 comments

Comments

@0angelic0
Copy link

It can be very useful if Async can use with async operations.

Right now, I think Async can use only with sync operations.

Async.background {
  // do some "sync" stuff 1
}.background {
  // do some "sync" stuff 2 after sync stuff 1
}

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.

Async.background {
  // async upload
  Alamofire.upload(.POST, "http://httpbin.org/post", file: fileURL)
    .reponseJSON { (_, _, JSON, _) in
        // upload done!!
    }
}.background {
  // do some stuff S after upload done!! ------>> Sorry S() is doing right away and not wait
  S()
}

An idea for accomplish this is something like passing a done block and waiting for done() to be called to continue to the next Async chain. I saw Quick/Nimble use this for waitUntil operation here.

So, here is a use case proposal.

Async.background { done in
  // async upload
  Alamofire.upload(.POST, "http://httpbin.org/post", file: fileURL)
    .reponseJSON { (_, _, JSON, _) in
        // upload done!!
        done()
    }
}.background {
  // do some "sync" stuff S1 after upload done!!
  S1()
}.background { done in
  // do some "async" stuff A1 after S1
  A1 { done() }
}.main {
  // All uploading, S1 and A1 are done, updates UI here
}

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?

@adad184
Copy link

adad184 commented May 8, 2015

i'm wondering this situation too :). In some situations, we do async network request in background.

@duemunk
Copy link
Owner

duemunk commented May 8, 2015

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" 😆

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.

@duemunk duemunk changed the title Async for async operations Async for non-GCD async operations May 8, 2015
@0angelic0
Copy link
Author

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.

@angelcasado
Copy link

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.

First without semaphores.

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.

With semaphores

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.

@eneko
Copy link
Contributor

eneko commented Feb 3, 2016

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!

@JoshHrach
Copy link

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.

@duemunk
Copy link
Owner

duemunk commented Aug 5, 2016

@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!

@JoshHrach
Copy link

@duemunk I'll do that tomorrow. Thanks.

@JoshHrach
Copy link

@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.

@duemunk
Copy link
Owner

duemunk commented May 20, 2019

Project is in maintenance mode, so I'm closing this issue. Thanks for everyone's work!

@duemunk duemunk closed this as completed May 20, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants