keeping track of state in asynchronous callbacks

5

Click here to load reader

Upload: paul-houle

Post on 06-May-2015

843 views

Category:

Technology


2 download

DESCRIPTION

There’s a lot of confusion about how asynchronous communication works in RIA’s such as Silverlight, GWT and Javascript. When I start talking about the problems of concurrency control, many people tell me that there aren’t any concurrency problems since everything runs in a single thread. [1] It’s important to understand the basics of what is going on when you’re writing asynchronous code, so I’ve put together a simple example to show how execution works in RIA’s and how race conditions are possible. This example applies to Javascript, Silverlight, GWT and Flex, as well as a number of other environments based on Javascript. This example doesn’t represent best practices, but rather what can happen when you’re not using a proactive strategy that eliminates concurrency problems

TRANSCRIPT

Page 1: Keeping track of state in asynchronous callbacks

Generation 5 » Keeping Track Of State In Asynchronous Callbacks

http://gen5.info/q/2008/06/02/keeping-track-of-state-in-asynchronous-callbacks/[1/12/2014 9:30:30 PM]

Keeping Track Of State In Asynchronous CallbacksWhen you’re writing applications that use asynchronous callbacks (i.e. Silverlight,AJAX, or GWT) you’ll eventually run into the problem of keeping track of the contextthat a request is being done in. This isn’t a problem in synchronous programming,because local variables continue to exist after one function calls another functionsynchronously:int AddToCount(int amount,string countId) { int countValue=GetCount(countId); return countValue+amount;}

This doesn’t work if the GetCount function is asynchronous, where we need to writesomething likeint AddToCountBegin(int amount,string countId,CountCallback outerCallback) { GetCountBegin(countId,AddToCountCallback);}

void AddToCountCallback(int countValue) { ... some code to get the values of amount and outerCallback ... outerCallback(countValue+amount);}

Several things change in this example: (i) the AddToCount function gets broken upinto two functions: one that does the work before the GetCount invocation, and onethat does the work after GetCount completes. (ii) We can’t return a meaningful valuefrom AddToCountCallback, so it needs to ‘return’ a value via a specified callbackfunction. (iii) Finally, the values of outerCallback and amount aren’t automaticallyshared between the functions, so we need to make sure that they are carried oversomehow.There are three ways of passing context from a function that calls and asynchronousfunction to the callback function:

1. As an argument to the callback function2. As an instance variable of the class of which the callback function is a class3. Via a closure

Let’s talk about these alternatives:

1. Argument to the Callback Function

In this case, a context object is passed to the asynchronous function, which passesthe context object to the callback. The advantage here is that there aren’t anyconstraints on how the callback function is implemented, other than by accepting thecontext object as a callback. In particular, the callback function can be static. A majordisadvantage is that the asynchronous function has to support this: it has to accept astate object which it later passes to the callback function.

The implementation of HttpWebRequest.BeginGetResponse(AsyncCallback a,Objectstate) in the Silverlight libraries is a nice example. If you wish to pass a context objectto the AsyncCallback, you can pass it in the second parameter, state. Your callbackfunction will implement the AsyncCallback delegate, and will get something thatimplements IAsyncResult as a parameter. The state that you passed intoBeginGetResponse will come back in the IAsyncResult.AsyncState property. Forexample:class MyHttpContext { public HttpWebRequest Request; public SomeObject FirstContextParameter; public AnotherObject AnotherContextParameter;}

protected void myHttpCallback(IAsyncResult abstractResult) { MyHttpContext context = (MyHttpContext) abstractResult.AsyncState; HttpWebResponse Response=(HttpWebResponse) context.Request.EndGetResponse(abstractResult);}

Search for:

ArchivesJune 2012 (1)August 2010 (1)May 2010 (1)June 2009 (2)April 2009 (1)March 2009 (1)February 2009 (3)January 2009 (3)November 2008 (1)August 2008 (2)July 2008 (5)June 2008 (5)May 2008 (2)April 2008 (6)March 2008 (8)June 2006 (1)February 2006 (1)

CategoriesAJAX (2)Asynchronous Communications (16)Biology (1)Books (1)Design (1)Distributed (1)Exceptions (2)Functional Programming (1)GIS (1)Ithaca (1)Japan (1)Math (1)Media (3)Nature (1)Semantic Web (3)Tools (28)

CRUD (1)Dot Net (17)Freebase (2)GWT (9)Java (7)Linq (2)PHP (6)Server Frameworks (1)Silverlight (12)SQL (5)

Uncategorized (1)Web (2)

Analytics (1)

Subscribe to our RSS Feed | About Us

Page 2: Keeping track of state in asynchronous callbacks

Generation 5 » Keeping Track Of State In Asynchronous Callbacks

http://gen5.info/q/2008/06/02/keeping-track-of-state-in-asynchronous-callbacks/[1/12/2014 9:30:30 PM]

public doHttpRequest(...) { ... MyHttpContext context=new MyHttpContext(); context.Request=Request; context.FirstContextParameter = ... some value ...; context.AnotherContextParameter = .. another value ...; Request.BeginGetResponse(); Request.Callback(myHttpCallback,context);}

Note that, in this API, the Request object needs to be available in myHttpCallbackbecause myHttpCallbacks get the response by calling theHttpWebResponse.EndGetResponse() method. We could simply pass the Requestobject in the state parameter, but we’re passing an object we defined,myHttpCallback, because we’d like to carry additional state into myHttpCallback.

Note that the corresponding method for doing XMLHttpRequests in GWT, the use of aRequestBuilder object doesn’t allow using method (1) to pass context information —there is no state parameter. in GWT you need to use method (2) or (3) to passcontext at the RequestBuilder or GWT RPC level. You’re free, of course, to use method(1) when you’re chaining asynchronous callbacks: however, method (2) is morenatural in Java where, instead of a delegate, you need to pass an object reference todesignate a callback function.

2. Instance Variable Of The Callback Function’s Class

Functions (or Methods) are always attached to a class in C# and Java: thus, the stateof a callback function can be kept in either static or instance variables of theassociated class. I don’t advise using static variables for this, because it’s possible formore than one asynchronous request to be flight at a time: if two request store statein the same variables, you’ll introduce race conditions that will cause a world of pain.(see how race conditions arise in asynchronous communications.)

Method 2 is particularly effective when both the calling and the callback functions aremethods of the same class. Using objects whose lifecycle is linked to a singleasynchronous request is an effective way to avoid conflicts between requests (see theasynchronous command pattern and asynchronous functions.)

Here’s an example, lifted from the asynchronous functions article: public class HttpGet : IAsyncFunction<String> { private Uri Path; private CallbackFunction<String> OuterCallback; private HttpWebRequest Request;

public HttpGet(Uri path) { Path = path; }

public void Execute(CallbackFunction<String> outerCallback) { OuterCallback = outerCallback; try { Request = (HttpWebRequest)WebRequest.Create(Path); Request.Method = "GET"; Request.BeginGetRequestStream(InnerCallback,null); } catch (Exception ex) { OuterCallback(CallbackReturnValue<String>.CreateError(ex)); } }

public void InnerCallback(IAsyncResult result) { try { HttpWebResponse response = (HttpWebResponse) Request.EndGetResponse(result); TextReader reader = new StreamReader(response.GetResponseStream()); OuterCallback(CallbackReturnValue<String>.CreateOk(reader.ReadToEnd())); } catch(Exception ex) { OuterCallback(CallbackReturnValue<String>.CreateError(ex)); } } }

Note that two pieces of context are being passed into the callback function: anHttpWebRequest object named Request (necessary to get the response) and aCallbackFunction<String> delegate named OuterCallback that receives the returnvalue of the asynchronous function.

Unlike Method 1, Method 2 makes it possible to keep an unlimited number of contextvariables that are unique to a particular case in a manner that is both typesafe and

Page 3: Keeping track of state in asynchronous callbacks

Generation 5 » Keeping Track Of State In Asynchronous Callbacks

http://gen5.info/q/2008/06/02/keeping-track-of-state-in-asynchronous-callbacks/[1/12/2014 9:30:30 PM]

oblivious to the function being called — you don’t need to cast an Object tosomething more specific, and you don’t need to create a new class to hold multiplevariables that you’d like to pass into the callback function.

Method 2 comes into it’s own when it’s used together with polymorphism, inheritanceand initialization patterns such as the factory pattern: if the work done by therequesting and callback methods can be divided into smaller methods, a hierarchy ofasynchronous functions or commands can reuse code efficiently.

3. Closures

In both C# and Java, it’s possible for a method defined inside a method to haveaccess to variables in the enclosing method. In C# this is a matter of creating ananonymous delegate, while in Java it’s necessary to create an anonymous class.

Using closures results in the shortest code, if not the most understandable code. Insome cases, execution proceeds in a straight downward line through the code —much like a synchronous version of the code. However, people sometimes getconfused the indentation, and, more seriously, parameters after the closure definitionand code that runs immediately after the request is fired end up in an awkward place(after the definition of the callback function.) public class HttpGet : IAsyncFunction<String> { private Uri Path;

public HttpGet(Uri path) { Path = path; }

public void Execute(CallbackFunction<String> outerCallback) { OuterCallback = outerCallback; try { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Path); Request.Method = "GET"; Request.BeginGetRequestStream(delegate(IAsyncResult result) { try { response = request.EndGetResponse(result); TextReader reader = new StreamReader(response.GetResponseStream()); outerCallback(CallbackReturnValue<String>.CreateOk(reader.ReadToEnd())); } catch(Exception ex) { outerCallback(CallbackReturnValue<String>.CreateError(ex)); }

},null); // <--- note parameter value after delegate definition

} catch (Exception ex) { outerCallback(CallbackReturnValue<String>.CreateError(ex)); } } }

The details are different in C# and Java: anonymous classes in Java can access local,static and instance variables from the enclosing context that are declared final — thismakes it impossible for variables to be stomped on while an asynchronous request isin flight. C# closures, on the other hand, can access only local variables: most of thetime this prevents asynchronous requests from interfering with one another, unless asingle method fires multiple asynchronous requests, in which case counter-intuitivethings can happen.

Conclusion

In addition to receiving return value(s), callback functions need to know somethingabout the context they run in: to write reliable applications, you need to be consciousof where this information is; better yet, a strategy for where you’re going to put it.Closures, created with anonymous delegates (C#) or classes (Java) produce theshortest code, but not necessarily the clearest. Passing context in an argument to thecallback function requires the cooperation of the called function, but it makes fewdemands on the calling and callback functions: the calling and callback functions canboth be static. When a single object contains both calling and callback functions,context can be shared in a straightforward and typesafe manner; and when the callingand callback functions can be broken into smaller functions, opportunities for efficientcode reuse abound.

Page 4: Keeping track of state in asynchronous callbacks

Generation 5 » Keeping Track Of State In Asynchronous Callbacks

http://gen5.info/q/2008/06/02/keeping-track-of-state-in-asynchronous-callbacks/[1/12/2014 9:30:30 PM]

Comments (6)

Login

Thomas Hansen +1

admin 0

Thomas Hansen 0

admin 0

Paul Houle on June 2nd 2008 in Asynchronous Communications, Dot Net, GWT,Java, Silverlight

Comments (6)Sort by: Date Rating Last Activity

· 292 weeks ago

· 292 weeks ago

· 292 weeks ago

· 292 weeks ago

Or you could use an Ajax Framework that queues up the requests which is the only sane thing to dowhen you have a server/client solution and waits for the previous one to finish before creating a newone ;)

(Hint; follow my link)

Nice blog though, and VERY important to be aware of, though probably less than 1% of ASP.NET AJAXdevelopers are... :)

Reply

@Thomas, your solution is a correct way to use AJAX and makes a lot of sense.

There is one case where it does make sense to generate concurrent requests, and that's wheninformation from several requests is going to be combined. Running requests in parallel can greatlyreduce the effects of latency -- in many cases this can make a large difference in performance (muchmore than two.)

I think most people doing AJAX in Javascript can get away with being ignorant about concurrencyproblems because they're bolting simple behaviors onto an HTML page: when something does gowrong, the situation is usually recoverable. I know that I programmed a little AJAX here and there foryears and never experienced concurrency problems -- then I built a GWT app!

People working with tools like Silverlight, GWT and Flex (as well as a few brave Javascript Ninjas) arebuilding larger applications that are going to have more problems. It's time to spread the word aboutthe problems and the solutions!

Reply

@admin

Yes I totally agree, in fact I by "chance" stumbled into the problem when adding up a timer onto mypage and then frenetically clicked a button at the same time and by accident discovered thatsometimes the ViewState went completely *bananas* and the page became in an "undefined state"...

Then I spent more than a WEEK in fact re-creating our core to give support for sequential requests...

I think that yes you are right, there might exist some extreme scenarios where it makes sense to havemore than one running at the same time, though if they're both running towards the same server, youwill get OTHER problems (2 HTTP connections per IP constraint in most browsers)

So I think probably if you are going to use them you would normally have them first of all run towardsdifferent IPs (servers) then also in addition it would be *WISE* to make sure they're not updating thesame parts of the UI...

Though for us (Gaia Ajax Widgets) this is completely abstract and would never happen unless peoplecompletely mis-used our framework in ways it was never intended to be used for...

On the server creating more than one request towards OTHER servers (WebServices and so on)asynchronously though is a totally different story...

.t

Reply

Well, in a perfect world, we'd design the client-server protocol so that all the information that the clientneeds to handle a UI event comes in one big batch... Then you don't need to worry aboutchoreography, error handling and all those hairy issues. See this article.

In the real world we need to talk to server apps that we don't control... So we have to deal with thoseissues.

Page 5: Keeping track of state in asynchronous callbacks

Generation 5 » Keeping Track Of State In Asynchronous Callbacks

http://gen5.info/q/2008/06/02/keeping-track-of-state-in-asynchronous-callbacks/[1/12/2014 9:30:30 PM]

Comment as a Guest, or login:

Displayed next to your comments. Not displayed publicly. If you have a website, link to it here.

Generation 5 » Getting back to the UI Thread in Silverlight 2 Beta 2[...] but what if you want to pass some parameters to the function that updates the UI. The usual threechoices for passing state in asynchronous applications apply, but it’s particularly easy and fun to use aclosure [...]

Generation 5 » Converting A Synchronous Program Into An Asynchronous Program[...] it’s useful for f() to correspond to an object, of which the fN() functions are methods. [1] [2] [...]

Post a new comment

Subscribe to None

Copyright © 2013 Generation 5. WordPress Theme design.

Reply

Name Email Website (optional)

Submit Comment