在C# vNext里面使用异步语法

threenewbee 2011-09-19 10:53:53
加精
今天用了一个下午加上晚上体验了一回 Visual Studio 11 Developer Preview。

下面详细介绍下 C# vNext(我不知道它应该是C#5还是C#4.5)的异步语法。最关键的是2个新增加的关键字(实际上在AnsyncCTP+VS2010就有,不过和VS11的略有不同)

目前网上已经有了不少文章,不过都是围绕微软给出的一个HTTP异步访问的程序讨论,为了不重复已有的内容,我设计了另一个测试代码,以便大家更多了解异步语法。

我的测试代码如下:

private int foo(int seed)
{
Random r = new Random(seed);
string s = "";
int i = 0;
while (s != "this")
{
char c1 = (char)r.Next(67, 122);
char c2 = (char)r.Next(67, 122);
char c3 = (char)r.Next(67, 122);
char c4 = (char)r.Next(67, 122);
s = new string(new char[] { c1, c2, c3, c4 });
i++;
}
return i;
}


代码的作用是不断随机产生一个由4个小写字母组成的字符串,直到这个字符串为“this”停止,返回尝试的次数。有一个故事说让猴子打字,只要尝试足够长的时间,他也能写出莎士比亚的著作。我的程序让计算机打字,要想输出一个有意义的单词(this),你会发现居然要这么多尝试,哈哈。这个代码没什么意义,但是可以做到2点,一个是它很占用CPU运算资源,另一个是运行时间长短无法预计,并且差异很大。这两点在使用异步算法的时候具有很好的演示作用。

开始我试图在一个控制台程序中实现异步语法,但是发现没有办法运行。无论是我的代码还是微软的Demo,都是如此,虽然可以编译,但是主程序会直接返回,而异步方法似乎没有执行。即便在主程序里面使用Task.Wait()也无济于事。这个问题直到现在仍然没有解决,估计和控制台程序的线程模型有关,如果谁知道原因,请指教下,谢谢!

之后我使用了WPF程序来测试。选择WPF的原因是它的EventHandler是支持异步的。

首先给出同步版本的调用,这个大家都很熟悉:
int[] results =
Enumerable.Range(0, 10)
.Select(x => foo(unchecked((int)(DateTime.Now.Ticks >> x)))).ToArray();
return "Result is: " + string.Join(", ", results) + ".";


这个程序把那个 foo() 调用了10次,并且搜集10次运行的结果并且输出。

然后我们在一个按钮的处理事件中调用它,把结果输出到文本框。

之后就是我们的重点,异步版本的程序:

int[] results =
await Task.WhenAll(Enumerable.Range(0, 10)
.Select(x => Task.Factory.StartNew(() => foo(unchecked((int)(DateTime.Now.Ticks >> x))))));
return "Result is: " + string.Join(", ", results) + ".";


代码的区别在于,我们构造了10个Task<int>分别执行这10次任务(int是Task的返回值,它代表一个Func<int>),然后我们调用 Task.WhenAll 等待10次运行结束。

WhenAll() 返回一个 Task<int[]> 类型。而使用 await 以后,它会自动将 Task<int[]> 执行,等待全部结束,填入 int[] 作为结果。而在执行任务的时候,主线程是不会被阻塞的。

注意,只有当一个方法被修饰为 async 的时候,才能使用 await 关键字。所以我们的方法为:
        private async Task<string> bar1()
{
int[] results =
await Task.WhenAll(Enumerable.Range(0, 10)
.Select(x => Task.Factory.StartNew(() => foo(unchecked((int)(DateTime.Now.Ticks >> x))))));
return "Result is: " + string.Join(", ", results) + ".";
}


我们还需要在事件处理函数中使用异步调用:
        private async void Button1_Click(object sender, RoutedEventArgs e)
{
this.textBox1.Text = await bar1();
}


异步语法其实是一个语法糖,它的作用是将 await 之后的代码从方法代码中转移到一个委托,并且在异步调用执行完成后执行,同时恢复现场,似乎好像它是同步的一样。

这一点,异步执行非常类似C# 2的yield return——迭代器也是编译器完成的魔术,每次迭代,当前状态都会被保存,在迭代继续执行的时候被恢复。因为是编译器魔术,这也解释了VS2010安装了AsyncCTP之后也可以使用类似的语法。事实上从IL的角度看,是看不到async的存在的。

这是完整的代码:
MainWindow.xaml
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="WpfApplication1.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="async" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Click="Button1_Click"/>
<TextBox x:Name="textBox1" HorizontalAlignment="Left" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Margin="0,26,0,0" Width="509" Height="42"/>
<Button Content="sync" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="0,67,0,0" Click="Button2_Click"/>
<TextBox x:Name="textBox2" HorizontalAlignment="Left" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Margin="0,93,0,0" Width="509" Height="42"/>
</Grid>
</Window>

MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private async void Button1_Click(object sender, RoutedEventArgs e)
{
this.textBox1.Text = await bar1();
}

private void Button2_Click(object sender, RoutedEventArgs e)
{
this.textBox2.Text = bar2();
}

private int foo(int seed)
{
Random r = new Random(seed);
string s = "";
int i = 0;
while (s != "this")
{
char c1 = (char)r.Next(67, 122);
char c2 = (char)r.Next(67, 122);
char c3 = (char)r.Next(67, 122);
char c4 = (char)r.Next(67, 122);
s = new string(new char[] { c1, c2, c3, c4 });
i++;
}
return i;
}

private async Task<string> bar1()
{
int[] results =
await Task.WhenAll(Enumerable.Range(0, 10)
.Select(x => Task.Factory.StartNew(() => foo(unchecked((int)(DateTime.Now.Ticks >> x))))));
return "Result is: " + string.Join(", ", results) + ".";
}

private string bar2()
{
int[] results =
Enumerable.Range(0, 10)
.Select(x => foo(unchecked((int)(DateTime.Now.Ticks >> x)))).ToArray();
return "Result is: " + string.Join(", ", results) + ".";
}

}
}


下面运行这个程序,使用异步语法的直观好处有2点:
- 程序运行得更快
- 程序响应更加流畅



这张图显示了2个版本的程序运行时CPU消耗的时间和利用效率。我找了一个四核心的处理器做这个测试,如图显示,同步版本(右边那个)只使用了25%的CPU,运行时间(图表x轴,也就是宽度)比左边的长。



这张图是运行同步版本时候的窗口,明显此时窗口失去了响应,用户不能拖动窗口,像死机了一样,标题栏也显示无响应字样。

对比下这张图:


在异步执行的时候,程序窗口还可以接受响应。

最后再给一张VS2011的截图,从图上看,和VS2010区别不大。


另外VS11经常出现失去响应的情况,估计微软也试图解决这个问题。于是在程序失去响应超过几秒以后会出现一个报告对话框:

大家尽可能多加一些内存。
...全文
1331 61 打赏 收藏 转发到动态 举报
写回复
用AI写文章
61 条回复
切换为时间正序
请发表友善的回复…
发表回复
Manonloki 2011-09-30
  • 打赏
  • 举报
回复
好久没关注楼主的.NET 4.5探秘系列了。。。。。。忙工作 一直看MEF。。。。
TTNice 2011-09-26
  • 打赏
  • 举报
回复
学习了
MYLOVEISWHO 2011-09-26
  • 打赏
  • 举报
回复
学习学习
xonln 2011-09-25
  • 打赏
  • 举报
回复
我一直用的是Winform不知有什么改进没有,楼主花点时间帮我看一下~!!
perfecthcm123 2011-09-25
  • 打赏
  • 举报
回复
学习学习学习
sekaodemeili 2011-09-24
  • 打赏
  • 举报
回复
好贴,顶一下
liveths 2011-09-24
  • 打赏
  • 举报
回复
还没有用过这个东东。。。。。。。
雨后的吻 2011-09-23
  • 打赏
  • 举报
回复
好好向前辈学习
tianfang25 2011-09-23
  • 打赏
  • 举报
回复
学习了
tianfang25 2011-09-23
  • 打赏
  • 举报
回复
学习了。
skyworth98 2011-09-22
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 fangxinggood 的回复:]

吼吼,那是vs11对async有所改变了吧。

下面的代码在vs2010 ctp里正常,Main方法顶多是有个warn,执行结果也OK
[/Quote]

如果我没理解错的话,个人认为,main不应该被标记成async.......
kael_fk_ 2011-09-22
  • 打赏
  • 举报
回复


using System;
using System.Linq;
using System.Threading;

namespace ConsoleApplication1
{
class Program
{
public delegate string myString();

static void Main(string[] args)
{
ThreadPool.QueueUserWorkItem(h =>
{
var query = from x in Enumerable.Range(0, 10).AsParallel()
select x + "->" + foo(unchecked((int)(DateTime.Now.Ticks >> x)));
Console.WriteLine("Result is: {0}.", string.Join(", ", query.ToArray()));
});
Console.ReadKey();
}

private static int foo(int seed)
{
Random r = new Random(seed);
string s = "";
int i = 0;
while (s != "this")
{
char c1 = (char)r.Next(67, 122);
char c2 = (char)r.Next(67, 122);
char c3 = (char)r.Next(67, 122);
char c4 = (char)r.Next(67, 122);
s = new string(new char[] { c1, c2, c3, c4 });
i++;
}
return i;
}
}

}


CHL199127 2011-09-21
  • 打赏
  • 举报
回复
真的吗?回去运行运行一下!
uncle_bacon 2011-09-21
  • 打赏
  • 举报
回复
不错不错 学习中
shupo 2011-09-21
  • 打赏
  • 举报
回复
c#越来越精简了
zhouxingyu896 2011-09-21
  • 打赏
  • 举报
回复
学习
学习
wquanchao 2011-09-21
  • 打赏
  • 举报
回复
c#越来越精简了
lyf1058 2011-09-21
  • 打赏
  • 举报
回复
好好向你学习
lyf1058 2011-09-21
  • 打赏
  • 举报
回复
谢谢了
threenewbee 2011-09-20
  • 打赏
  • 举报
回复
[Quote=引用 9 楼 fangxinggood 的回复:]
一点体会:Task based 的异步编程模型,异步的编程复杂度小了。对Task的设计要求高了。
[/Quote]
Async最大的用途就是配合WPF、SL和Windows vNext的程序设计。如我我之前所说,Cloud+Client的程序严重依赖延迟不确定的网络通讯,异步编程任务无所不在。为此能简化一点语法是很必要的。
加载更多回复(37)

110,533

社区成员

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

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

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