我正在阅读Jon Skeet的书" C#in Depth"。在 15.2.2 上,我们有以下示例:
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!!
}
我有两个问题:
当返回类型为return length
时,第一种方法的Task<int>
如何工作?
我认为Console.WriteLine(lengthTask.Result);
阻止了UI线程。我使其工作的唯一方法是将其更改为:lengthTask.ContinueWith(t => Console.WriteLine(t.Result), TaskContinuationOptions.ExecuteSynchronously);
这是对的吗?
答案 0 :(得分:1)
我认为是Console.WriteLine(lengthTask.Result);阻止UI线程。我使其工作的唯一方法是将其更改为:lengthTask.ContinueWith(t =&gt; Console.WriteLine(t.Result),TaskContinuationOptions.ExecuteSynchronously);这是对的吗?
是的,这是正确的。 Console.WriteLine(lengthTask.Result)
将阻止您的UI线程,但它可以在控制台应用程序中正常工作,因为控制台应用程序使用线程池sycnrhonization上下文,它可以使用多个线程。
您在ContinueWith
采用的解决方案将起作用,这也是您可以使用async-await做的事情,它基本上会自动将方法的其余部分作为延续进行调度:
static async Task PrintPageLength()
{
Task<int> lengthTask =
GetPageLengthAsync("http://csharpindepth.com");
Console.WriteLine(await lengthTask.ConfigureAwait(false));
}
ConfigureAwait(false)
用于在异步工作完成后避免上下文切换,类似于TaskContinuationOptions.ExecuteSynchronously
使用的ContinueWith
。
如果返回类型为Task,第一种方法的返回长度如何?
Async-await代码不是你所看到的就是你得到的。它编译为状态机。如果您反汇编代码,它看起来更像是:
[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;
}
状态机看起来像这样:
[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);
}
}
答案 1 :(得分:1)
1 /如果你进一步阅读(15.3.5):
你可以看到长度的类型是int,但是返回类型 方法是任务。生成的代码负责包装 你,以便调用者得到一个最终会有的任务 完成后从方法返回的值。
2 /你是对的,对Result
的电话是一个阻止电话。这在Console应用程序中没有任何不便的后果,但它会在GUI中。您通常会在UI线程上使用await
来轻松防止这种情况:
myTextBlock.Text = await GetPageLengthAsync("http://csharpindepth.com");
这(当然是异步方法)也可以防止可能的死锁(见http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115163.aspx或http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html)
答案 2 :(得分:1)
当返回类型为
return length
时,第一种方法的Task<int>
如何工作?
正如我在async
intro中所描述的那样,async
关键字会执行两项操作:它启用await
关键字,并引入了一个更改结果处理方式的状态机。状态机的细节并不重要;你需要知道的是状态机负责构造和控制返回的Task<int>
。
当async
方法到达其第一个await
并异步等待该操作时,它将返回不完整的Task<int>
。稍后,当async
方法到达return
语句时,它会使用该结果值完成Task<int>
。
我认为
Console.WriteLine(lengthTask.Result);
阻止了UI线程。我使其工作的唯一方法是将其更改为:lengthTask.ContinueWith(t => Console.WriteLine(t.Result), TaskContinuationOptions.ExecuteSynchronously);
这是正确的吗?
没有
虽然该代码可以在这种情况下运行,但使用await
代替ContinueWith
会更好:
static async Task PrintPageLengthAsync()
{
Task<int> lengthTask = GetPageLengthAsync("http://csharpindepth.com");
Console.WriteLine(await lengthTask);
}
ContinueWith
有一些unsafe default options我在博客上详细描述。使用await
而不是ContinueWith
编写正确的代码要容易得多。