首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > .NET > C# >

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

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

在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> 


MainWindow.xaml.cs
C# code
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经常出现失去响应的情况,估计微软也试图解决这个问题。于是在程序失去响应超过几秒以后会出现一个报告对话框:

大家尽可能多加一些内存。

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

-----------

async void Main() { await ... } 的代码么?我好像也遇见过,好像拎出一个static方法再调用就OK了。
回头找找。
[解决办法]
建议楼主,不要用Linq的连缀写法,会让我们这些小菜头昏的。

虽然我知道楼主是个Linq狂
[解决办法]
想问一下 如果只调用一次怎么写。。。对异步不是很了解。。。昨天看了你的帖子了 特来求教


这个例子里是调用了1次方法。。。。如果只调用一次 那个task[]怎么给?
[解决办法]
贴一下对照的.net4.0写法(console的)吧:
C# code
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;        }    }} 

热点排行