Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

providing an asynchronous programming model: Should I? and if so, should it be VerbAsync() or BeginVerb()?

Providing Synchronous and Asynchronous versions of Method in c# asks how to provide an async version of a method.

One of the answers suggests that class library developers should avoid providing the async method, if possible, based on the single responsibility principle.

  1. Is this true?
    Should I NOT provide async versions of methods?

  2. If the answer is NO (in other words, DO provide async versions of methods), then should I follow the advice in this MSDN article, which states:

The IAsyncResult design pattern allows for a variety of programming models, but is more complex to learn and provides a flexibility that most applications do not require. Where possible, class library designers should implement asynchronous methods using the event-driven model. In some cases the library designer should also implement the IAsyncResult based model.

??

The "Event driven model" is one where , if the synchronous version of the method is Verb(), the async version of the method is VerbAsync(), and there is a VerbCompleted event.
The IAsyncResult pattern is the well-known BeginVerb() and EndVerb() approach.

like image 347
Cheeso Avatar asked Sep 03 '09 22:09

Cheeso


3 Answers

Why are you considering providing an asynchronous API?

Are the API operations IO bound or CPU bound? (is the reason it takes a long time because of waiting for IO to complete, or just it's CPU intensive?)

If CPU bound, I would consider whether it really is necessary to provide an asynchronous version, since the caller can always effectively convert a synchronous operation into an asynchronous operation via the threadpool.

The strongest reason for providing an asynchronous API is for operations which are IO bound. Rather than tying up threads to wait on high latency IO operations it is preferable that asynchronous IO is used. This can particularly affect the scalability of a system. For example, if you had a server blocking on a single synchronous IO operation, it's not too much of an issue - you're just tying up one thread. However, execute 1000 of the same operation concurrently and you're tying up 1000 threads (and if memory serves 1MB per thread for stack) just to wait on completion of operations which take few CPU cycles. End result - you've got an idle CPU but are sucking up resources.

So, if for example, you yourself were writing a socket library, for sure you would want to provide asynchronous IO operations, so that users of the library could write scalable applications.

If, for example, you're writing an encryption library, then even though the operations may take a long time, there's probably no pressing need to provide an async API - it's CPU bound anyway. And as mentioned the user can make it asynchronous.

Finally, if you're writing an IO bound system which uses lower level APIs that provide asynchronous IO (e.g. an FTP client that uses the socket classes) you may also want to consider providing an asynchronous version of your API. The thing is though doing this isn't easy - you only provide the scalability benefit if you make use of the lower level API's asynchronous functions. This can quickly end up turning simple synchronous logic into extremely complex, difficult to debug asynchronous logic. The main issue being all the state information you previously had nice simple access to via local variables end up needing to be manually captured so that when the next IO operation completes your logic knows what to do next.

Providing an asynchronous API which then makes calls internally to synchronous IO operations is kind of pointless (though I've seen it done). Gives the illusion of scalability, but... isn't!

When .NET 4.0 comes out, some of this may get a bit simpler (though I think from what I've seen it's still going to be tricky).

In the mean time, you might want to check out Jeffrey Richter's async enumerator library which can help simplify this (somewhat):

Jeffrey Richter on his async enumerator

Power threading library including async enumerator

Hope this helps, Phil.

NOTE: If you are going to implement an asynchronous API, I would recommend providing it via the 'classic begin/end' IAsyncResult API. The reason is, from what I remember, it should integrate much better with .NET 4.0's task parallel library.

like image 155
Phil Avatar answered Oct 20 '22 14:10

Phil


  1. I don't agree. If you know what you are doing. Use it!

  2. The Framework Design Guidelines recomend that you use Event-Based Async Pattern for higher-level APIs and Classic Async Pattern for lower-level APIs.

The Event-Based Async Pattern is easier to understand by novice developers and has better support on the IDE.

The Classic Async Pattern on the other hand, allows you to do more sofisticated stuff such as initiating multiple async operations and wait for the completion of one or all of them.

like image 2
Alfred Myers Avatar answered Oct 20 '22 12:10

Alfred Myers


Whether or not you provide this really depends a bit on what your methods are going to do. If it's easy enough to use your object in an asynchronous manner without this, then I would avoid implementing asynchronous methods within your object.

When .NET 4 is released, for example, the Task Parallel Library will make using a synchronous object asynchronously a trivial "Task" - which means its easy to just write one version of your software, and allow it to be used asynchronously or synchronously. It's fairly easy today using the threadpool.

However, if your method is, by its nature, going to be waiting on something outside of your control (ie: networking), then having asyncrhonous methods is critical. In this case, I think both options should be supported, both Begin*()/End*() and *Async()/*Completed. Both patterns have their use, and it's easy to implement both.


If you look at the MSDN page on Asynchronous Programming, there's a good clue as to the thinking that should help you decide whether this makes sense:

Asynchronous operations are typically used to perform tasks that might take a long time to complete

Note the word "might" in there. That's the key, for me, when determining whether the method should be asynchronous. If a task is going to wait on something else, and it might take a long time, or might return immediately, and this is out of my control, then it should be asynchronous.

If, on the other hand, it's always going to take a long time to run, I'd rather leave it up to the caller as to how to handle it. It's particularly easy to push this onto another thread:

 ThreadPool.QueueUserWorkItem( (o) => { DoWork(); }, null);

However, if the caller already is working in a background/separate thread, it's easier to just use it synchronously. Tasks in .NET 4 make it even easier:

 var result = Task<ReturnType>.Factory.StartNew( () => DoWork() );
 // It'll run, and if you want to do EndInvoke, you can do result.Result
like image 1
Reed Copsey Avatar answered Oct 20 '22 13:10

Reed Copsey