Moar tools for asynchrony!

721 views

Published on

Some thoughts on asynchrony in a modern world, inspired by Reactive Extensions and await in C#, for the Obj-C audience at CocoaHeads Stockholm June 2012.

Please read the notes on each slide to make sense of them, the slides are not understandable by themselves.

Published in: Technology, Design
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
721
On SlideShare
0
From Embeds
0
Number of Embeds
7
Actions
Shares
0
Downloads
3
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide
  • Hi. I don't have a presentation as much as some material for discussion and a few thoughts. At cocoaheads here a few months ago, I realized that all the presentations were about asynchrony in some manner. Asynchrony seems to be on a lot of people's minds nowadays, more now than ever. In particular, I've seen Microsoft doing two really interesting things recently, but we'll get to that.
  • Let's start of by defining asynchrony: it's any task that you have to or choose to wait for the completion of. I can think of three classes of such tasks: - Background computation - I/O (network, disk, …) - Events (touch, keyboard, app activation, wifi connected, value changes ( KVO )…) Many programming languages are very sequential in nature, so how do you write code to cope with asynchrony? There are two extremely common ways: blocking, and callbacks.
  • Blocking or synchronous code is extremely easy to follow, as every intermediate step is laid out sequentially, and dependencies are clearly visible. Fetching events, doing IO, and doing CPU-intensive work all laid out in the order it needs to. However, this (contrived) example is unacceptable, as locks up the main thread for several seconds as soon as you try to interact with the UI. It's common to use this pattern on a background thread, but this has its own set of downsides: you can't really cancel the work being done, you're locking up resources unnecessarily, and it might not even be less complex than a non-blocking counterpart since you still need to do thread communication.
  • You've all used callbacks in one form or another. It's a convenient pattern to be able to return from your current scope, yet return to a connected scope once your operation is complete or your event triggers. For delegating behavior, callbacks work great. Once you get to the situation where you have a chain of things you want to wait for asynchronously however, things can get really hairy.
  • Even with fancy closures, this is pretty horrible. Error handling is spread all over the place, memory management is a mess, and this code isn't even handling cancellation. While we're using the same primitive —closures— for all the callbacks, we don't really have an abstraction for asynchrony here: we don't have a single concept wrapping all our different kind of callbacks. If we did, we might have been able to say things like "for all the asynchronous tasks in this method, register a cancellation handler that is triggered when this view controller goes off-screen", or "start a UIBackgroundTask for as long as we have any outstanding IO", or something much simpler like just "wait for all of these things to finish", or "use this error handler for all asynchronous errors in this method". In this example, we might want to set an error image whichever error we get. So, here's my current pet peeve: "blocking" and "callbacks" aren't the only two ways in which you can handle asynchronous code. Consider alternatives whenever you're writing code that is starting to resemble the above. Let's look at some.
  • Futures are proxy objects that either don't trigger at all until you need them (upper), or runs in the background immediately but don't become blocking until you actually use them (lower). For example, if you store futureData here in an instance variable and it takes a while before you use it, you might never need to block. Since it's in the nature of Futures to actually *do* block if you ask for their value too early, I'm not a big fan of them in ObjC. However, we can use them as inspiration to encapsulate asynchronous tasks in an object oriented manner.
  • I'll admit any day to being a total Apple-head. In the past two years I've barely even looked at anything that isn't Objective-C, and such a narrow view really isn't good for your development as a programmer. As some of you might be able to tell from this code though, I recently stumbled across some very handy concepts in C# and .NET. In C#, Task is a single class used to wrap asynchrony, no matter where it comes from. Once we have one single abstraction for asynchronous things, we can start to explore this power. If we like callbacks, we can use callbacks just like before.
  • If we're *already* on a background thread, we can write blocking code, with the same primitives.
  • But instead of writing sequential blocking code, we can now parallelize tasks, even though the come from different subsystems and wrap different kinds of asynchrony: in this case, computation and I/O. We can also cancel all the tasks with a single abstraction.
  • In the C# that comes with Windows 8, they're actually building these concepts into the language. There’s a new “await” keyword that you can use whenever you encounter an asynchronous task. This actually ends execution of the method, as if you had typed “return”, and returns a Task object to the caller. The caller can then choose to either wait on it in turn, or pass it on, or whatever. When the web request comes in, the method automatically continues to execute from the point where it paused, and repeats this pattern until the method is finished. This really blew my mind, and I really hope that Apple has something similar up their sleeve next week, or they are soon going to look very stone age.
  • Without the await keyword, the method might look something like this. I don’t really know C# so I’m faking the block syntax here... It’s the same code as before, but broken into a tree of nested closures, which is immediately a lot harder to read. However, with await, you're writing code that looks like normal, blocking sequential code, but it is in fact asynchronous and cancelable. That's pretty amazing.
  • We don't have coroutines in Objective-C, so if we wanted to at least approximate this power in our favorite language, how might we go about it? Well, turns out that this feature is specific to C#, and in languages like Managed C++, you'll have to use chained closures. If I were to ever finish my port of this called "TCTask", it might look a little like this. The idea is that every asynchronous task has a method called 'then', which calls a callback when it has finished processing. This callback can return a new TCTask which indicates more asynchronous work to be done, which you can call 'then' on in turn, and so on and so on. This way, you end up with a chain of callbacks that look at least a bit nicer than nested callbacks, and still gives us all the flexibility of asynchrony-as-an-object that I've been ranting about.
  • The next really cool thing Microsoft has done recently is Reactive Extensions for C#. The whole idea behind RX is to provide tools for *composing asynchronous tasks* — basically applying functional programming tools to asynchrony. This library has been ported to cocoa in the form of ReactiveCocoa, or RAC, and is the subject for the presentation after mine, so I'll just highlight it really quickly. I experimented with wrapping AsyncSocket in RAC and ended up with something where you could write a fully working network client with error handling in 7 lines of code.
  • To summarize, I keep relearning two life lessons in programming, and they apply to asynchrony like they do to everything else: * if you have a concept or piece of state in your code that is not represented by an actual object, go make it an object. * Don't settle for mediocrity. If you feel that your code is ugly, someone has probably thought of a way to make it pretty. If not, you can probably figure it out on your own if you give it enough thought.
  • Moar tools for asynchrony!

    1. 1. Moar tools for asynchrony! @nevyn
    2. 2. • Background computation Building image, database search, (anything threaded)• I/O Network Disk touch app activation• Events value changes (KVO) keyboard wifi connected
    3. 3. while(event = getNextEvent()) { // async event // possibly async IO, polling NSURL *pictureURL = [[Backend currentUser] profilePictureURL]; // possibly async IO NSData *pictureData = [NSData dataWithContentsOfURL:pictureURL]; // computation UIImage *profilePicture = [UIImage imageWithData:pictureData]; if ([event touches:someRect]) { // IO UIImage *highlight = [UIImage imageNamed:@"profile-picture-highlight"]; // computation profilePicture = [highlight imageCompositedOver:profilePicture]; } [profilePicture drawInRect:someRect];}
    4. 4. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange,audioRouteChangeListenerCallback,NULL);-[NSOperation setCompletionBlock:(void (^)(void))block];
    5. 5. // async event- (void)touchesBegan:(NSArray*)touches withEvent:(UIEvent*)event { // async IO [[Backend currentUser] getProfilePictureURL:^ (NSURL*url) { // async IO [HTTPLibrary fetchURL:url onCompletion:^ (NSData *data) { // async processing dispatch_async(dispatch_get_global_queue(0,0), ^{ UIImage *profilePicture = [UIImage imageWithData:data]; … highlight … dispatch_async(dispatch_get_main_queue(), ^{ imageView.image = profilePicture; }; } } error:^(NSError* err) { ... }]; } onError:(NSError *err) { … }];}
    6. 6. NSString *filename = ...;NSData *maybeData = MALazyFuture(^{ return [NSData dataWithContentsOfFile: filename];});[object doSomethingOrNotWithData: maybeData];NSString *filename = ...;NSData *futureData = MABackgroundFuture(^{ return [NSData dataWithContentsOfFile: filename];});[object doSomethingLaterWithData: futureData];
    7. 7. NSString *filename = ...;Task *futureData = TCBackgroundTask(^{ return [NSData dataWithContentsOfFile: filename];});futureData.onDoneCallback = ^ (NSData *data) { [object doSomethingLaterWithData: futureData];};[futureData startOnQueue:dispatch_get_global_queue(0,0)];
    8. 8. Task *task = [[Backend currentUser] profilePictureURL];NSURL *profilePictureURL = [task runSynchronously];
    9. 9. // I/OTask *pictureTask = [UIImage imageFromURL:profilePictureURL];// computationTask *badgeTask = [UIImage imageNamedTask:@"profile-picture-highlight"];// I/OTask *colorTask = [[Backend currentUser] favoriteColor];// Wait for all of the above to complete[Task waitAll:pictureTask, badgeTask, colorTask, nil cancelOn:[CancellationToken cancelOnDismissed:self]];// Use the fetched or computed valuesUIImage *badgedImage = [[badgeTask value] composeOnTopOf: [pictureTask value]];UIImage *coloredImage = [badgedImage tintedImage: [colorTask value]];
    10. 10. private async Task<byte[]> GetURLContentsAsync(string url){ var content = new MemoryStream(); HttpWebRequest webReq = WebRequest.Create(url); // GetResponseAsync returns a Task<WebResponse>. WebResponse response = await webReq.GetResponseAsync(); // “Pause” execution Stream responseStream = response.GetResponseStream(); // CopyToAsync returns a Task await responseStream.CopyToAsync(content); // “Pause” execution return content.ToArray();}
    11. 11. private async Task<byte[]> GetURLContentsAsync(string url){ var content = new MemoryStream(); HttpWebRequest webReq = WebRequest.Create(url); // GetResponseAsync returns a Task<WebResponse>. WebResponse response = await webReq.GetResponseAsync(); Stream responseStream = response.GetResponseStream(); // CopyToAsync returns a Task await responseStream.CopyToAsync(content); private Task<byte[]> GetURLContentsAsync(string url) { return content.ToArray(); var content = new MemoryStream();} HttpWebRequest webReq = WebRequest.Create(url); Task<WebResponse> responseTask = webReq.GetResponseAsync(); Task task2 = responseTask.startAndCallback(^(WebResponse response) { Stream responseStream = response.GetResponseStream(); Task copyTask = responseStream.CopyToAsync(content); Task<byte[]> bytesTask = copyTask.startAndCallback(^ { return content.ToArray(); }); return bytesTask; }; return task2; }
    12. 12. TCTask* GetURLContentsAsImageAsync(NSURL *url) { return [[[NSURLConnection requestURL:url] then:^ (id response) { return [response getDataAsync]; } then:^ (NSData *data) { return TCBackgroundTask(^{ [UIImage imageWithData:data]; }); }];}
    13. 13. [[[TCRACSocket connectTo:@"localhost" port:1236]selectMany:^id<RACSubscribable>(id x) { return [x lines];}] subscribeNext:^(id x) { self.label.text = x;} error:^(NSError *error) { NSLog(@"Failure: %@", error);}];
    14. 14. • Objects <3• Get un-stuck!

    ×