2015-02-11 41 views
0

我正在阅读Jon Skeet的书“C#深度”。在15.2.2我们有下面的例子:从T到任务<T>的转换工作原理如何?修复任务。结果块到UI线程

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

我这里有两个问题:

  1. 它是如何发生的是在第一种方法return length工作时,返回类型为Task<int>

  2. 我认为Console.WriteLine(lengthTask.Result);会阻塞UI线程。我使它工作的唯一方法是将其更改为:lengthTask.ContinueWith(t => Console.WriteLine(t.Result), TaskContinuationOptions.ExecuteSynchronously); 这是正确的吗?

回答

1

我认为Console.WriteLine命令(lengthTask.Result);阻止UI线程。我使它工作的唯一方法是将其更改为:lengthTask.ContinueWith(t => Console.WriteLine(t.Result),TaskContinuationOptions.ExecuteSynchronously);它是否正确?

是的,这是正确的。 Console.WriteLine(lengthTask.Result)将阻止您的UI线程,但它在控制台应用程序中可以正常工作,因为控制台应用程序使用线程池sycnrhonization上下文,其中包含多个线程。

ContinueWith通过将工作的解决方案,这是后话,你还可以做异步的await,基本上自动调度方法作为延续的休息:

static async Task PrintPageLength() 
{ 
    Task<int> lengthTask = 
    GetPageLengthAsync("http://csharpindepth.com"); 
    Console.WriteLine(await lengthTask.ConfigureAwait(false)); 
} 

ConfigureAwait(false)用于在异步工作完成后避免上下文切换,类似于您使用TaskContinuationOptions.ExecuteSynchronouslyContinueWith

在返回类型为Task时,第一种方法的返回长度是如何工作的?

异步等待代码不是你所看到的是你得到的。它编译成一个状态机。如果你拆开你的代码,它看起来更像是这样的:

[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

感谢您提供丰富的答案。不过,我测试过'.ConfigureAwait(false); +在WinForms上等待lengthTask'并崩溃。它适用于'ConfigureAwait(true)' – 2015-02-11 07:43:07

+0

对不起,应该是'Console.WriteLine(await lengthTask.ConfigureAwait(false));',否则会出现编译器错误。我今天早上测试了它,它工作。 – 2015-02-11 22:58:22

1

1 /如果你继续读下去(15.3.5):

您可以看到长度类型是int,但方法的返回类型是Task。生成的代码负责打包 你,以便调用者获得一个任务,该任务最终会在方法完成时从该方法返回的值为 。

2 /您是对的,拨打电话Result是阻止呼叫。这在控制台应用程序中没有不方便的后果,但它在GUI中。您通常会使用await在UI线程上,以防止容易:

myTextBlock.Text = await GetPageLengthAsync("http://csharpindepth.com"); 

本(当然在异步方法)也将防止可能的死锁(见http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115163.aspxhttp://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

+0

我是新的,我必须进一步阅读:)而是,我花了几个小时的实验。最后决定这是TPL魔术的一部分。 – 2015-02-11 07:51:18

+1

一定要检查链接,我不记得乔恩是否在他的书中谈论。如果可能,请始终使用'async/await',这样更容易阅读和防止一些同步问题。 – 2015-02-11 07:54:29

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容易得多。

+0

谢谢。我开始阅读你的并发系列。我觉得它非常有用。你的书看起来也很有趣,正是我现在正在学习的东西。 – 2015-02-11 15:25:36