日期:2014-05-20  浏览次数:20851 次

在C# vNext里面使用异步语法
今天用了一个下午加上晚上体验了一回 Visual Studio 11 Developer Preview。

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

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

我的测试代码如下:

C# code
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是支持异步的。

首先给出同步版本的调用,这个大家都很熟悉:
C# code
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次运行的结果并且输出。

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

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

C# code
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 关键字。所以我们的方法为:
C# code
        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) + ".";
        }


我们还需要在事件处理函数中使用异步调用:
C# code
        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
XML code
<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>