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!
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.
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
.
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.