async/await的疑问

黑娃 2016-06-17 03:50:04
private Task<string> doAsyncWork()
{
return Task.Run(() =>
{
Thread.Sleep(10000);
return "done";
});
}

private async void btn_test_Click(object sender, RoutedEventArgs e)
{
try
{
btn_test.Content = await doAsyncWork();
MessageBox.Show("haha");
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}

上面代码的运行结果是:点击按钮后,等待10秒,然后按钮的文字变成done,接着弹出对话框haha,但是神奇的是,整个过程中,包括等待的10秒过程中,界面都是可以响应的!!请大家看看下面这两句:
btn_test.Content = await doAsyncWork();
MessageBox.Show("haha");
第一句表示调用线程A(也就是UI线程)开启了一个线程B,B在等待10秒,此时A并没有输出haha,说明A是阻塞在第一句的,但为什么这10秒过程中,UI都能响应?A不是被阻塞挂起了吗?
...全文
184 6 打赏 收藏 转发到动态 举报
写回复
用AI写文章
6 条回复
切换为时间正序
请发表友善的回复…
发表回复
masanaka 2016-06-17
  • 打赏
  • 举报
回复
引用 4 楼 sp1234 的回复:
我认为,语法上看更良好的设计是传统的回调形式。例如
var x= GetElementId();
WaitUserClicked(x, callback);
.......
这个代码很干净地“委托callback”是异步回调,逻辑上并不阻塞地等到callback被执行才执行 .......代码,而是可能先执行 .......代码。 而 async/await 语法则只能完成上述前2句(不能直接实现前3句),它硬要让你把 callback 写成跟 GeetElementId() 同步顺序执行一模一样的语法,但是你要自己用脑子去把 await 再理解为回调,因为你使用 async/await 语法时你会看到对应于 ......的那部分是提前执行了、并没有被callback所阻塞,这时候你就反而疑惑了。 这就是 async/await 语法很不好的地方。 但是它有什么好处呢?假设有连缀的一堆异步回调,例如
a(()=>{
  b(x=>{
      c(x+1, ()=>{
           d(y=>{
              f(y);
          };
       });
   });
});
这样的代码就看上去很垃圾,那么使用 async/await 就比较清爽。 其实用宰牛刀杀鸡,如果在一个群体成了习惯,那么可能这个群体也不知道还有轻量级的刀子了。
多谢P哥指点。
黑娃 2016-06-17
  • 打赏
  • 举报
回复
引用 4 楼 sp1234 的回复:
我认为,语法上看更良好的设计是传统的回调形式。例如
var x= GetElementId();
WaitUserClicked(x, callback);
.......
这个代码很干净地“委托callback”是异步回调,逻辑上并不阻塞地等到callback被执行才执行 .......代码,而是可能先执行 .......代码。 而 async/await 语法则只能完成上述前2句(不能直接实现前3句),它硬要让你把 callback 写成跟 GeetElementId() 同步顺序执行一模一样的语法,但是你要自己用脑子去把 await 再理解为回调,因为你使用 async/await 语法时你会看到对应于 ......的那部分是提前执行了、并没有被callback所阻塞,这时候你就反而疑惑了。 这就是 async/await 语法很不好的地方。 但是它有什么好处呢?假设有连缀的一堆异步回调,例如
a(()=>{
  b(x=>{
      c(x+1, ()=>{
           d(y=>{
              f(y);
          };
       });
   });
});
这样的代码就看上去很垃圾,那么使用 async/await 就比较清爽。 其实用宰牛刀杀鸡,如果在一个群体成了习惯,那么可能这个群体也不知道还有轻量级的刀子了。
明白了,感谢两位的回答,这个语法糖确实有点误导人,之前对传统回掉都习惯了反而觉得这个顺序式的很奇怪,真是哭笑不得
  • 打赏
  • 举报
回复
我认为,语法上看更良好的设计是传统的回调形式。例如
var x= GetElementId();
WaitUserClicked(x, callback);
.......
这个代码很干净地“委托callback”是异步回调,逻辑上并不阻塞地等到callback被执行才执行 .......代码,而是可能先执行 .......代码。 而 async/await 语法则只能完成上述前2句(不能直接实现前3句),它硬要让你把 callback 写成跟 GeetElementId() 同步顺序执行一模一样的语法,但是你要自己用脑子去把 await 再理解为回调,因为你使用 async/await 语法时你会看到对应于 ......的那部分是提前执行了、并没有被callback所阻塞,这时候你就反而疑惑了。 这就是 async/await 语法很不好的地方。 但是它有什么好处呢?假设有连缀的一堆异步回调,例如
a(()=>{
  b(x=>{
      c(x+1, ()=>{
           d(y=>{
              f(y);
          };
       });
   });
});
这样的代码就看上去很垃圾,那么使用 async/await 就比较清爽。 其实用宰牛刀杀鸡,如果在一个群体成了习惯,那么可能这个群体也不知道还有轻量级的刀子了。
Forty2 2016-06-17
  • 打赏
  • 举报
回复
这是因为编译器做了手脚。btn_test_Click由编译器重组为类似如下代码:

private void btn_test_Click(object sender, RoutedEventArgs e)
{
    Task<string> t = doAsyncWork();
    t.ContinueWith(WhenTaskFinished, TaskScheduler.FromCurrentSynchronizationContext());  // 1,2
}  // 3

private void WhenTaskFinished(Task<string> task) // 4
{
    try
    {
        btn_test.Content = task.Result;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.ToString());
    }
}
你可以观察到: 1、await就是一种ContinueWith,它登记一个’回调函数‘,这个回调函数(即那个WhenTaskFinished)将在Task完成后得到调用。 2、await同时也捕获了当前的同步环境,即那个TaskScheduler.FromCurrentSynchronizationContext, 并只在捕获的同步环境下运行’回调函数‘。 3、await马上返回。 因为await启动task后马上返回UI,因此UI不会被阻塞。 因为await捕获的同步环境就是UI,因此回调将在UI线程上执行,而不用麻烦Dispatcher.Invoke来同步到UI。
  • 打赏
  • 举报
回复
如果你在多一些地方进行调试,你就会发现在 await 之前是顺序执行的,然后突然跳出到其它代码里边去了,然后又在其它代码跟 Task.Run 中注册的代码部分来回乱跳,最后又跳回 await 后边的代码了。 实际执行流程就是如此!await 就是一个回调,所以 btn_test_Click 执行到 wait 之后并不会阻塞界面。因为 btn_test_Click 已经结束了,根本没有在当时就在 btn_test_Click 方法中执行 await 后边(左边)的代码。
  • 打赏
  • 举报
回复
await 就是异步回调。也就是相当于
                btn_test.Content = doCallback(delegate
                {
                           ...............这里是 await 之前的代码
                           doAsyncWork();  
                },
               resultValue =>
               {
                      ..........这里是await之后的代码
                      MessageBox.Show("haha");
}); 这里所谓的“用顺序流程的语法来写一个异步多线程程序”是个障眼法,它其实是专门制造假象的。 实际上,btn_test_Click 方法在执行到 await 的地方就已经结束了,后边的都是回调执行代码。所以语法 async 放到方法修饰中,不让你调试到 btn_test_Click 方法结束部分。以此让程序员看不清楚回调的本意。

110,538

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧