如何从T转换为Task <t>?修复task.Result块到UI线程</t>

时间:2015-02-11 05:29:10

标签: c# task-parallel-library async-await

我正在阅读Jon Skeet的书&#34; C#in Depth&#34;。在 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!!
}

我有两个问题:

  1. 当返回类型为return length时,第一种方法的Task<int>如何工作?

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

3 个答案:

答案 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.aspxhttp://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编写正确的代码要容易得多。