4

I'm new to asynchronous programming in C# and I'm still confused about a few things. I've read that after .NET 4.5, the APM and EAP are no longer recommended for new development since the TAP is supposed to replace them (source).

I think I understood how async/await works and I'd be able to use them for performing IO operations that have async methods. For example, I could write an async method that awaits for an HttpWebClient's GetStringAsync result, since it's declared as an async method. That's great.

My question is: what if we have an IO operation that happens in a method that is not declared as async? Like this: suppose I have an API that has a method

string GetResultFromWeb()

which queries something from the Web. And I have lots of different queries to do and I must use this method to do so. And then I need to process each query result. I understand that I'd do this if that was an async method:

Task<string> getResultTask = GetResultFromWeb(myUrl); 
// Do whatever I need to do that doesn't need the query result
string result = await getResultTask;
Process(result);

But since it's not, I cannot await for it -- it tells me string is not awaitable. So my question is: is there any way of performing these IO operations asynchronously without having to create one thread for each query? If I could, I'd like to create as less threads as possible, without having to block any of the threads.

One way I found to do so was by implementing APM, following this article from Jeffrey Richter and then, in my Begin method, I call ThreadPool.QueueWorkItem(GetResultFromWeb, asyncResult). Like this:

public class A {
    private void DoQuery(Object ar){
        AsyncResult<string> asyncResult = (AsyncResult<string>) ar;
        string result = GetResultFromWeb();
        asyncResult.SetAsCompleted(result, false);
    }

    public IAsyncResult BeginQuery(AsyncCallback){
        AsyncResult<string> asyncResult = new AsyncResult<string>(callback, this);
        ThreadPool.QueueUserWorkItem(DoQuery, asyncResult);
        return asyncResult;
    }

    public string EndQuery(IAsyncResult ar){
        AsyncResult<string> asyncResult = (AsyncResult<string>)ar;
        return asyncResult.EndInvoke();
    }
}

Then I use an AsyncEnumerator and begin (BeginQuery) several queries and process the results as each one of them finishes (using yield return / EndQuery). This seems to work well. But after reading so much that APM is obsolete, I was wondering how could I do this using TAP. Also, is there any problem with this APM approach?

Thanks!

Derek Patton
  • 223
  • 2
  • 8

3 Answers3

4

Your API is asynchronous using the older Begin/End model. This fits into TPL via

Task.Factory.FromAsync<string>(BeginQuery, EndQuery)

which returns a Task<string> which you can await.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Aside from make the code simpler, is there any advantage (performance) of doing this instead of just sticking to the old Begin/End method? – Derek Patton Jan 06 '15 at 01:32
  • @DerekPatton: It's much easier to interleave with other async calls. – Ben Voigt Jan 06 '15 at 01:34
  • 1
    @DerekPatton: Also, I don't know why you think there could be a performance advantage. A wrapper is never faster than the foundation it rests on. – Ben Voigt Jan 06 '15 at 01:35
  • 1
    You said that my API is using the Begin/End model. It is not. Please note that I created the Begin/End methods myself. Does this mean that I'd have to create Begin/End methods and then use this Task.Factory.FromAsync call every time I'd want to await non-async methods? Also, does this creates a new thread per call? – Derek Patton Jan 06 '15 at 01:45
  • @DerekPatton: as long as your API fits the standard signatures, you can use Ben's suggestion. Note that there isn't an overload that matches your `BeginQuery` method exactly (as near as I can tell...there are a lot of them and I might have missed something :) ), but you can _call_ the method and pass the result to `FromAsync`: `Task.Factory.FromAsync(BeginQuery(MyQueryCallback), EndQuery);` (where `MyQueryCallback` is your callback method, the one you'd have passed to `BeginQuery()` regardless). – Peter Duniho Jan 06 '15 at 02:53
  • 1
    @DerekPatton: No, you wouldn't want to write Begin/End methods just for the purpose of wrapping them. But there are a lot of existing APIs that use Begin/End. To have good performance, you need to rewrite your `GetResultFromWeb` to use some sort of async API for network I/O. Whether Begin/End, TPL async, or whatever, they can all be turned into async. In your particular case, it might even be better to proxy the request using async APIs, and call the third-party API with a local URL that responds immediately. – Ben Voigt Jan 06 '15 at 04:26
4

what if we have an IO operation that happens in a method that is not declared as async?

In this case, the I/O operation is blocking. In other words, GetResultFromWeb blocks the calling thread. Keep that in mind as we go through the rest...

I must use this method to do so.

By this I infer that you cannot write a GetResultFromWebAsync method which is asynchronous. So any thread doing the web requests must be blocked.

is there any way of performing these IO operations asynchronously without having to create one thread for each query?

The most natural approach is to write a GetResultFromWebAsync method. Since that isn't possible, your options are: block the calling thread, or block some other thread (i.e., a thread pool thread). Blocking a thread pool thread is a technique I call "fake asynchrony" - since it appears asynchronous (i.e., not blocking the UI thread) but it's really not (i.e., it just blocks a thread pool thread instead).

If I could, I'd like to create as less threads as possible, without having to block any of the threads.

That's not possible given the constraints. If you must use the GetResultFromWeb method, and that method blocks the calling thread, then a thread must be blocked.

One way I found to do so was by implementing APM, following this article from Jeffrey Richter and then, in my Begin method, I call ThreadPool.QueueWorkItem(GetResultFromWeb, asyncResult).

In this case, your code is exposing an asynchronous API (begin/end), but in the implementation it's just calling GetResultFromWeb on a thread pool thread. I.e., it is fake asynchrony.

This seems to work well.

It works, but it's not truly asynchronous.

But after reading so much that APM is obsolete, I was wondering how could I do this using TAP.

As others have noted, there's a much easier way to schedule work to the thread pool: Task.Run.

True asynchrony isn't possible, because you have a blocking method that you must use. So, all you can do is a workaround - fake asynchrony, a.k.a. blocking a thread pool thread. The easiest way to do that is:

Task<string> getResultTask = Task.Run(() => GetResultFromWeb(myUrl)); 
// Do whatever I need to do that doesn't need the query result
string result = await getResultTask;
Process(result);

(much cleaner code than APM and AsyncEnumerator)

Note that I do not recommend creating a GetResultFromWebAsync method that is implemented using fake asynchrony. The Task-returning, Async-suffix methods are supposed to follow the Task-based Asynchronous Pattern guidelines, which imply true asynchrony.

In other words, as I describe in more detail on my blog, use Task.Run to invoke a method, not to implement a method.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
1

A simpler way to do what you are looking for is to call the method using the Task class. In your case it would look something like this:

Task<string> getResultTask = Task.Run<string>(()=>GetResultFromWeb(myUrl));
// Do whatever I need to do that doesn't need the query result
string result = await getResultTask;
Process(result);

While this will create another thread as your IAsyncResult option does, this greatly simplifies the process.

Jacob Lambert
  • 7,449
  • 8
  • 27
  • 47
  • 2
    Good answer, however having an `async` method that just delegates to `Task.Run` is not recommended because `async` methods don't usually start a new thread. – NeddySpaghetti Jan 06 '15 at 01:13
  • Nice, but isn't this creating a new thread for each call of GetResultFromWebAsync? I thought APM would be more efficient since it wouldn't create a different thread for each call, but rather enqueue tasks and alternate a much smaller number of threads for those tasks, therefore removing the overheads of creating new threads. Am I wrong? – Derek Patton Jan 06 '15 at 01:15
  • @DerekPatton I believe the `Task` class used the `ThreadPool` internally if I remember what I read is correct. – Jacob Lambert Jan 06 '15 at 01:19
  • @NedStoyanov what would be a better way to implement this then? This is the best way I currently know of to make a non async method act as an acync method, but I'm always looking to improve. – Jacob Lambert Jan 06 '15 at 01:21
  • 1
    I think the recommendation is to not add the method ending xxxxAsync just use `Task.Run` directly – NeddySpaghetti Jan 06 '15 at 01:31
  • @DerekPatton I was correct in my thinking that `Task` uses the CLR thread pool internally. – Jacob Lambert Jan 06 '15 at 02:04