Reputation: 972
I use HttpClient and I want to write something like that:
HttpClient client = ...;
Task<string> getContent()
{
return client.GetAsync(...)
.ContinueWith( t => t.Result.Content.ReadAsStringAsync() );
}
I know I can write
.ContinueWith( t => t.Result.Content.ReadAsStringAsync().Result);
but one of pool's threads will be blocked. I want continueWithTask like in the Google Task library.
How can I accomplish it?
UPDATE
And yes, I do need to use Tasks instead of async/await and I really know what I want.
UPDATE2
I revised my views and now I think that I was wrong in choosing the technology. If anyone doubts, here is a great example of good code.
Upvotes: 0
Views: 2060
Reputation: 457472
And yes, I do need to use Tasks instead of async/await and I really know what I want.
I strongly recommend Marc's answer. I cannot think of a good reason not to use async
/await
unless you are stuck on .NET 4.0 (i.e., Windows XP). And that can't be the case because you're using HttpClient
and Task.Run
. So please bear in mind that this answer is purely instructional and not recommended for production.
ContinueWith
calls can be "chained", kind-of similar to how Promise.then
works in JavaScript, but the out-of-the-box C# chaining semantics are more awkward than JavaScript's.
For one thing, Task<Task<T>>
types are not automatically unwrapped. There is an Unwrap
method available. For another, the use of .Result
- a TPL relic more naturally used with ContinueWith
- will wrap exceptions in an AggregateException
, which can cause an interesting kind of "cascade" where your inner exception can get buried deeply inside nested AggregateException
instances. Hence the presence of AggregateException.Flatten
to straighten out the mess after-the-fact. Oh, and you should always pass a TaskScheduler
to ContinueWith
.
Here's a first attempt, explicitly specifying the TaskScheduler
, using Unwrap
to unwrap nested tasks, and avoiding the nested exceptions by using GetAwaiter().GetResult()
instead of Result
:
Task<string> getContent()
{
// I am so sorry, future coder, but I cannot use await here because <insert good reason>
return Task.Run(()=> client.GetAsync(...))
.ContinueWith(t => t.GetAwaiter().GetResult().Content.ReadAsStringAsync(), TaskScheduler.Default).Unwrap()
.ContinueWith(t => t.GetAwaiter().GetResult(), TaskScheduler.Default);
}
If you do this a lot in your code, you may want to use something like .Then
that encapsulates some of this logic. Oh, and be sure to write an apology in the comments; even if the future maintainer is yourself, that's just the polite thing to do with code like this. ;)
Upvotes: 3
Reputation: 171246
but one of pool's threads will be blocked
That's not the case because the ContinueWith
delegate is only called once the "antecedent" task has completed. Result
will not block and it's even synchronization free.
The second .Result
(ReadAsStringAsync().Result
) is blocking, though. So you have to turn that into another ContinueWith
.
In general, a sequence of sequential IOs turns into a sequence of ContinueWith
.
In general, await
is preferable here but you indicated that this would not work for you.
Upvotes: 0
Reputation: 1064244
These days you should avoid ContinueWith
, preferring async
/await
unless you have very very specific reasons; I suspect this will work:
async Task<string> getContent()
{
var foo = await client.GetAsync(...); // possibly with .ConfigureAwait(false)
return await foo.Content.ReadAsStringAsync(); // possibly with .ConfigureAwait(false)
}
Upvotes: 6