从 T 到 Task<T> 的转换是如何工作的?将 task.Result 块固定到 UI 线程
How conversion from T to Task<T> works? Fixing task.Result block to UI thread
我正在阅读 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 类型为 Task<int>
时,第一种方法中的 return length
如何工作?
我认为 Console.WriteLine(lengthTask.Result);
阻塞了 UI 线程。我让它工作的唯一方法是将其更改为:lengthTask.ContinueWith(t => Console.WriteLine(t.Result), TaskContinuationOptions.ExecuteSynchronously);
这是正确的吗?
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?
是的,这是正确的。 Console.WriteLine(lengthTask.Result)
会阻塞您的 UI 线程,但它在控制台应用程序中可以正常工作,因为控制台应用程序使用线程池同步上下文来灰化多个线程。
您在 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
一起使用。
How it happens that the return length on the first method works when the return type is 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/ 如果您进一步阅读 (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/ 没错,对 Result
的调用是阻塞调用。这在控制台应用程序中没有不便的后果,但它会在 GUI 中。您通常会在 UI 线程上使用 await
来轻松防止这种情况发生:
myTextBlock.Text = await GetPageLengthAsync("http://csharpindepth.com");
这(当然是在异步方法中)也将防止可能的死锁(参见 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)
How it happens that the return length
on the first method works when the return type is Task<int>
?
正如我在 async
intro 中所描述的那样,async
关键字做了两件事:启用 await
关键字,并引入了一个状态机来更改结果的处理方式.状态机的细节并不那么重要;你需要知道的是,状态机负责构造和控制返回的Task<int>
。
当 async
方法到达其第一个 await
并异步等待该操作时,它 returns 是一个不完整的 Task<int>
。稍后,当 async
方法到达 return
语句时,它会使用该结果值完成 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?
没有
虽然该代码在这种情况下可以工作,但最好使用 await
而不是 ContinueWith
:
static async Task PrintPageLengthAsync()
{
Task<int> lengthTask = GetPageLengthAsync("http://csharpindepth.com");
Console.WriteLine(await lengthTask);
}
ContinueWith
有很多 unsafe default options 我在我的博客上详细描述了。使用 await
比 ContinueWith
.
更容易编写正确的代码
我正在阅读 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 类型为
Task<int>
时,第一种方法中的return length
如何工作?我认为
Console.WriteLine(lengthTask.Result);
阻塞了 UI 线程。我让它工作的唯一方法是将其更改为:lengthTask.ContinueWith(t => Console.WriteLine(t.Result), TaskContinuationOptions.ExecuteSynchronously);
这是正确的吗?
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?
是的,这是正确的。 Console.WriteLine(lengthTask.Result)
会阻塞您的 UI 线程,但它在控制台应用程序中可以正常工作,因为控制台应用程序使用线程池同步上下文来灰化多个线程。
您在 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
一起使用。
How it happens that the return length on the first method works when the return type is 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/ 如果您进一步阅读 (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/ 没错,对 Result
的调用是阻塞调用。这在控制台应用程序中没有不便的后果,但它会在 GUI 中。您通常会在 UI 线程上使用 await
来轻松防止这种情况发生:
myTextBlock.Text = await GetPageLengthAsync("http://csharpindepth.com");
这(当然是在异步方法中)也将防止可能的死锁(参见 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)
How it happens that the
return length
on the first method works when the return type isTask<int>
?
正如我在 async
intro 中所描述的那样,async
关键字做了两件事:启用 await
关键字,并引入了一个状态机来更改结果的处理方式.状态机的细节并不那么重要;你需要知道的是,状态机负责构造和控制返回的Task<int>
。
当 async
方法到达其第一个 await
并异步等待该操作时,它 returns 是一个不完整的 Task<int>
。稍后,当 async
方法到达 return
语句时,它会使用该结果值完成 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?
没有
虽然该代码在这种情况下可以工作,但最好使用 await
而不是 ContinueWith
:
static async Task PrintPageLengthAsync()
{
Task<int> lengthTask = GetPageLengthAsync("http://csharpindepth.com");
Console.WriteLine(await lengthTask);
}
ContinueWith
有很多 unsafe default options 我在我的博客上详细描述了。使用 await
比 ContinueWith
.