在C# vNext里面使用异步语法
下面详细介绍下 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经常出现失去响应的情况,估计微软也试图解决这个问题。于是在程序失去响应超过几秒以后会出现一个报告对话框:

大家尽可能多加一些内存。
相关推荐
matrix-vnext:MatriX vNext
MatriX vnext 自述文件仍在进行中。 现在转到: :
vNext.BlazorComponents:打造Blazor高性能UI组件库
Blazor 是一个开源的 Web 框架,它允许开发者使用 C# 来编写前端的 Web 应用程序,而不需要离开 .NET 生态。Blazor 通过在浏览器中运行 WebAssembly 来实现这一目标,这意味着开发者可以利用现有的 .NET 技能和代码库来构建丰富的交互式 Web 应用。Blazor 开发的核心是基于组件的模型,即页面和用户界面由一系列可重用的组件构成。每个组件都封装了自己的逻辑、数据和标记,这使得开发过程更加模块化、易于管理和维护。
vNext.BlazorComponents:Blazor的高性能UI组件
vNext.BlazorComponents:Blazor的高性能UI组件
Abp vNext学习手册v1.0
【Abp vNext学习手册v1.0】适合新手,如果您觉得Abp官网的文档不是很能理解那么这款文档可快速带你入门!
mongodb-mva-vnext:重写 Microsoft Virtual Academy of C# MongoDB 示例的 sedouardmongodb-mva 的副项目,以便在 vNext KRE 中运行
vNext 中的 #MongoDB MVA C# 文件 这是 Microsoft Virtual Academy C# / MongoDB示例的存储库的小型配套项目,重写后可在 OS X/Yosemite(或任何其他KRE支持的系统)上的vNext KRE 中运行。 在观看此在线课程的模块 4 的同时进行研发和编写: 来自@MSVirtAcademy 的。 我喜欢 MongoDB 并在我公司的工作中使用 C#。 我已经获得了 NodeJS 和 MongoDB 的证书 - 但之前没有使用C#和MongoDB组合的经验。 我假设您知道vNext是什么: : 我希望你会发现这很有用。 ####TL;DR 下载这个 repo,切换到项目文件夹并从控制台运行: kpm restore kpm build k run ##项目 ##项目描述 ##里面有什么 ###CSharpSt


