Reputation: 3383
I'm reading the Jon Skeet's book "C# in Depth". On 15.2.2 we have the following example:
static async Task<int> GetPageLengthAsync(string url)
{
using (HttpClient client = new HttpClient())
{
Task<string> fetchTextTask = client.GetStringAsync(url);
int length = (await fetchTextTask).Length;
return length; // How this is converted to Task<int> ?
}
}
static void PrintPageLength()
{
Task<int> lengthTask =
GetPageLengthAsync("http://csharpindepth.com");
Console.WriteLine(lengthTask.Result); // This blocks the UI thread!!
}
I have two problems here:
How it happens that the return length
on the first method works when the return type is Task<int>
?
I think that the Console.WriteLine(lengthTask.Result);
blocks the UI thread. The only way I made it working was by changing it to: lengthTask.ContinueWith(t => Console.WriteLine(t.Result), TaskContinuationOptions.ExecuteSynchronously);
Is this correct?
Upvotes: 0
Views: 110
Reputation: 13495
I think that the Console.WriteLine(lengthTask.Result); blocks the UI thread. The only way I made it working was by changing it to: lengthTask.ContinueWith(t => Console.WriteLine(t.Result), TaskContinuationOptions.ExecuteSynchronously); Is this correct?
Yes this is correct. Console.WriteLine(lengthTask.Result)
will block your UI thread but it will work fine in a console app as console apps use a Thread Pool sycnrhonization context which ash multiple threads.
The solution you adopted with ContinueWith
will work, this is something you can also do with async-await, which essentially automatically schedules the rest of the method as a continuation:
static async Task PrintPageLength()
{
Task<int> lengthTask =
GetPageLengthAsync("http://csharpindepth.com");
Console.WriteLine(await lengthTask.ConfigureAwait(false));
}
The ConfigureAwait(false)
is used to avoid context swtiching after the asynchronous work is done, similar to the TaskContinuationOptions.ExecuteSynchronously
you use with ContinueWith
.
How it happens that the return length on the first method works when the return type is Task?
Async-await code is not what you see is what you get. It compiles to a state machine. If you disassemble your code it looks more like this:
[AsyncStateMachine(typeof(<GetPageLengthAsync>d__0)), DebuggerStepThrough]
private static Task<int> GetPageLengthAsync(string url)
{
<GetPageLengthAsync>d__0 d__;
d__.url = url;
d__.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
d__.<>1__state = -1;
d__.<>t__builder.Start<<GetPageLengthAsync>d__0>(ref d__);
return d__.<>t__builder.Task;
}
The state machine looks like this:
[CompilerGenerated]
private struct <GetPageLengthAsync>d__0 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder<int> <>t__builder;
private object <>t__stack;
private TaskAwaiter<string> <>u__$awaiter4;
public HttpClient <client>5__1;
public Task<string> <fetchTextTask>5__2;
public int <length>5__3;
public string url;
private void MoveNext()
{
int num;
try
{
bool flag = true;
switch (this.<>1__state)
{
case -3:
goto Label_0113;
case 0:
break;
default:
this.<client>5__1 = new HttpClient();
break;
}
try
{
TaskAwaiter<string> awaiter;
if (this.<>1__state != 0)
{
this.<fetchTextTask>5__2 = this.<client>5__1.GetStringAsync(this.url);
awaiter = this.<fetchTextTask>5__2.GetAwaiter();
if (!awaiter.IsCompleted)
{
this.<>1__state = 0;
this.<>u__$awaiter4 = awaiter;
this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>, Form1.<GetPageLengthAsync>d__0>(ref awaiter, ref this);
flag = false;
return;
}
}
else
{
awaiter = this.<>u__$awaiter4;
this.<>u__$awaiter4 = new TaskAwaiter<string>();
this.<>1__state = -1;
}
string result = awaiter.GetResult();
awaiter = new TaskAwaiter<string>();
int length = result.Length;
this.<length>5__3 = length;
num = this.<length>5__3;
}
finally
{
if (flag && (this.<client>5__1 != null))
{
this.<client>5__1.Dispose();
}
}
}
catch (Exception exception)
{
this.<>1__state = -2;
this.<>t__builder.SetException(exception);
return;
}
Label_0113:
this.<>1__state = -2;
this.<>t__builder.SetResult(num);
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine param0)
{
this.<>t__builder.SetStateMachine(param0);
}
}
Upvotes: 1
Reputation: 456417
How it happens that the
return length
on the first method works when the return type isTask<int>
?
As I describe in my async
intro, the async
keyword does two things: it enables the await
keyword, and it introduces a state machine that changes how the results are handled. The details of the state machine are not that important; what you need to know is that the state machine is responsible for constructing and controlling the Task<int>
that is returned.
When the async
method reaches its first await
and asynchronously waits for that operation, it returns an incomplete Task<int>
. Later, when the async
method reaches a return
statement, it completes the Task<int>
with that result value.
I think that the
Console.WriteLine(lengthTask.Result);
blocks the UI thread. The only way I made it working was by changing it to:lengthTask.ContinueWith(t => Console.WriteLine(t.Result), TaskContinuationOptions.ExecuteSynchronously);
Is this correct?
No.
While that code would work in this situation, it would be far better to use await
instead of ContinueWith
:
static async Task PrintPageLengthAsync()
{
Task<int> lengthTask = GetPageLengthAsync("http://csharpindepth.com");
Console.WriteLine(await lengthTask);
}
ContinueWith
has a number of unsafe default options that I describe in detail on my blog. It is far easier to write correct code with await
than ContinueWith
.
Upvotes: 1
Reputation: 3744
1/ If you read further (15.3.5):
You can see that the type of length is int, but the return type of the method is Task. The generated code takes care of the wrapping for you, so that the caller gets a Task, which will eventually have the value returned from the method when it completes.
2/ You're right, the call to Result
is a blocking call. This has no unconvenient consequence in a Console app, but it would in a GUI. You would typically use await
on the UI thread to prevent that easily:
myTextBlock.Text = await GetPageLengthAsync("http://csharpindepth.com");
This (in an async method of course) will also prevent possible deadlock (see http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115163.aspx or http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html)
Upvotes: 1