Async operations but no callback or state?

Mar 1, 2010 at 4:59 PM

Hello!  Your library looks very interesting but I really need callbacks when async operations are complete.

May I ask, how come you didn't follow the standard .NET asynchronous design patterns?  As it is now I have to poll if the operations are complete or block until they are. Neither of these is very optimal in my project.

Thanks for your time!

- Chris

Coordinator
Mar 1, 2010 at 6:06 PM

Most of the .NET framework components that implement async behavior offer blocking/polling and callbacks. So I don't agree that I didn't follow the standard design patterns. Rather, I implemented a subset of the standard patterns.

Not a single person ever requested async support before I added the current implementation. So I implemented the most basic, generalized async support that would meet my own needs and leave open the possibility of future async enhancements (i.e. the async support is implemented as extension methods rather than being tacked on to the main API class).

 

Coordinator
Mar 1, 2010 at 8:12 PM

After taking another look at this, I realized you may be referring to the fact that an IAsyncResult is not returned. The IAsyncResult is actually a property of the AsyncAction and AsyncFunction objects returned from the ExecuteAsync extension method, but it's not publicly exposed. The main point in wrapping it was to provide async support with strong typing of return values without adding 36 additional methods to the main API class (e.g. BeginA, EndA, etc.).

Mar 2, 2010 at 5:56 PM

Returning IAsyncResult is only part of the pattern implementation.  Exposing Begin / End method pairs is another part, and accepting an AsyncCallback and object state is the last.  It's very common throughout the .NET libraries and developers are used to using it in this way, plus you can leverage other libraries that expect these types of patterns without making an adapter class.

Typically, rather than having the IAsyncResult as a property, you should have the class that is responsible for maintaining the state of your async call implement IAsyncResult.  This is the easiest way to do it.  In your case, if I'm understanding the library properly), AsyncOperation should implement IAsyncResult.  You would have to rework a few minor things but it's pretty simple.

By the way, implementing the async stuff as extension methods was brilliant. It made me think, it could be simplified (or further extended) by having something like below.  I will be using it this way on my end so I can get the callbacks:

        public static IAsyncResult BeginGet<T>(this ISimpleSavant savant, string itemName, AsyncCallback callback, object state)
        {
            Func<ISimpleSavant, T> async = s => s.Get<T>(itemName);
IAsyncResult ar = async.BeginInvoke(savant, callback, state); } private T EndGet<T>(IAsyncResult ar)
{ var func = (Func<ISimpleSavant, T>)(ar as AsyncResult).AsyncDelegate;
return func.EndInvoke(ar); }

 


 

 

 

 

            var a = new A2();
            Func<ISimpleSavant, A2> async = s => s.Get<A2>(a.ItemName);
            IAsyncResult ar = 

 

 

 

 

 

 

Coordinator
Mar 2, 2010 at 6:37 PM
Edited Mar 2, 2010 at 6:52 PM

Yes, good points and I generally agree with what you say. I wanted to add some minimal async support but didn't have much time to spend on the feature...this is where I ended up for now.

I am tinkering with something similar to your code sample, with a few extra gyrations necessary to maintain backwards compatibility and add async support for the ISimpleSavant2 methods (typeless operations).

Inventing lambdas and extension methods was brilliant, using it is only...,er, bright! ;) But thanks for the kind words and feedback!

 

Coordinator
Mar 2, 2010 at 9:15 PM

I've checked in a new version of Coditate.Savant.Async.AsyncExtensions with several methods prototyped according to the new pattern. Check it out if you get a chance.

Thanks for posting the above code sample. I did not realize the IAsyncResult returned from Delegate.BeginInvoke() carried a reference to the delegate itself or I would probably not have implemented the AsyncOperation classes in the first place.

 

Mar 2, 2010 at 10:26 PM

I looked over the changes and they look great!  Thanks!

Dec 16, 2010 at 8:51 AM

There's one more issue with the ASync extensions.

I've been working with a wider set of web calls and in performance profiling came across some interesting implementation details. The core implementation of Async WebRequests in .Net is cheaper than applying an Async pattern on top of a synchronous call.

http://stackoverflow.com/questions/4299101/thread-startwebrequest-getresponse-vs-webrequest-begingetresponse

http://social.msdn.microsoft.com/forums/en-US/winforms/thread/cef9a3d5-7bc7-429c-8f17-6b915f9ac648/

Each of those point out that the core implementation, with .BeginGetResponse(), uses Windows I/O Completion Ports. The process goes like this:

1) BeginGetResponse called.

2) Still on calling thread - DNS lookup begins, thread blocks synchronously.

3) DNS returns. Thread from threadpool selected, processing handed off to it.

4) Calling thread proceeds with next line of code; meanwhile, request checked against .Net max requests (default is 2)

http://msdn.microsoft.com/en-us/library/fb6y0fyc.aspx

5) If limit not hit, request proceeds. While waiting for response, thread is descheduled.

6) Async response begins (streaming may take a significant period of time, however). Could be callback, signaled wait handle, etc depending on how you called the request.

The most important lesson learned here is that the AsyncExtensions in SimpleSavant are missing out on this performance advantage, because they just call the synchronous GetResponse() which spins the thread until the call returns. On busy machines this is a substantial performance hit.

There are a couple other important points here for developers interested in Async Savant calls though: You need to up the maximum Requests to something suitable to how you use concurrent requests in your app, and you need to watch your current request total (probably an application-level integer you use a lock to increment and decrement) or you get some really strange failures - sometimes your requests go out with TCP window size 0, sometimes they fail silently, etc.

I'd like to suggest the Async needs another go that leverages the underlying I/O Completion Ports implementation (HttpWebRequest.BeginGetResponse()).

Coordinator
Dec 16, 2010 at 12:46 PM

@soophaman: Yes, those are all good points. However, Savant is built on top of the AWS SDK which supports only synchronous operations. The only options at this point are to wait for Amazon to expose true async calls, fork the Amazon library, or completely re-implement all the low-level SimpleDB functions in Savant. Unless someone else volunteers to do the work involved in option 2 or 3, Savant won't be using async IO until Amazon exposes that functionality in the SDK.

There was recently a long discussion about this on the Amazon .NET dev forum. I would encourage you to weigh in there. And thanks for taking the time to lay out the issue clearly here!

 

 

Dec 20, 2010 at 11:23 PM
Edited Dec 20, 2010 at 11:28 PM

And now there's an I/O Completion Ports implementation of the low-level AWS SDK SimpleDB calls:

http://asyncsimpledb.codeplex.com/

Ashley, let me know if you're interested in tying this into SimpleSavant. Happy to help!

Coordinator
Dec 21, 2010 at 2:25 AM

Thanks, I'll check it out and figure out what it would take to integrate!

Dec 21, 2010 at 2:44 AM

Awesome!

I deviated from the normal IAsyncResult pattern because I prefer to have strongly typed objects coming into the callback, but if you'd like me to make some changes let me know.

Dec 22, 2010 at 7:28 PM

I'm adding timeouts and exception handling - I now see why the IAsyncResult pattern requires you to call EndXXX even in a callback scenario - making you call End lets any caught exceptions be thrown from inside the End call.

I'll post a new version of this that takes these into account, probably by next week.