C#学习日志 day8 -------------- async await 异步方法入门(引用博客)以及序列化和反序列化的XML及json实现
首先是异步方法的介绍,这里引用自http://www.cnblogs.com/LoveJenny/archive/2011/11/01/2230933.html
async and await 简单的入门
如果有几个Uri,需要获取这些Uri的所有内容的长度之和,你会如何做?
很简单,使用WebClient一个一个的获取uri的内容长度,进行累加。
也就是说如果有5个Uri,请求的时间分别是:1s 2s 3s 4s 5s.
那么需要的时间是:1+2+3+4+5=(6*5)/2=15.
如果采用并行计算的话,结果可能是这样:
总时间长度是5s.
为了演示效果,需要下面3个页面:
其中SlowPage 的Page_load代码如下:
protected void Page_Load(object sender, EventArgs e)
{
Thread.Sleep(5000);
}
VerySlowPage的Page_load事件则 Thread.Sleep(10000);
新建控制台程序CAStudy:
首先新建类AsyncDemo:
同步的获取Uris的内容长度代码如下:
public class AsyncDemo { public int SumPageSizes(IList<Uri> uris) { int total = 0; foreach (var uri in uris) { Console.WriteLine("Thread {0}:Found {1} bytes...{2}", Thread.CurrentThread.ManagedThreadId, total,DateTime.Now); var data = new WebClient().DownloadData(uri); total += data.Length; } Console.WriteLine("{0}:Found {1} bytes total {2}", Thread.CurrentThread.ManagedThreadId, total, DateTime.Now); return total; } }
在这里SumPageSizes 方法,通过foreach循环一个一个的下载数据。
Main函数如下:
public static void Main() { List<Uri> uris = new List<Uri>(); uris.Add(new Uri("http://localhost:57815/AsyncTestPages/QuickPage.aspx")); uris.Add(new Uri("http://localhost:57815/AsyncTestPages/SlowPage.aspx")); uris.Add(new Uri("http://localhost:57815/AsyncTestPages/VerySlowPage.aspx")); uris.Add(new Uri("http://localhost:57815/AsyncTestPages/QuickPage.aspx")); uris.Add(new Uri("http://localhost:57815/AsyncTestPages/SlowPage.aspx")); uris.Add(new Uri("http://localhost:57815/AsyncTestPages/VerySlowPage.aspx")); AsyncDemo asyncDemo = new AsyncDemo(); int totalSize = asyncDemo.SumPageSizes(uris); }
Main 函数主要是构造Uri,然后调用AsyncDemo的SumPageSizes方法来获取所有Uri的内容的总长度。
结果如下:
可以看到时间分别是0s,5s,10s,0s ,5s,10s.所以总长度是(0+5+10)*2=30.
可以看到速度很慢,如果有一个网页卡住的话,后面很恐怖的哦
下面演示使用async,await的方式:
第一步:将 VS2010 升级到 VS2010 sp1.
第二步:下载Async CTP,进行安装
第三步:为应用程序添加AsyncCTPLibrary引用,如下:
OK,将上面的SumPageSizes 方法修改如下:
public async Task<int> SumPageSizesAsync2(IList<Uri> uris) { var tasks = uris.Select(uri => new WebClient().DownloadDataTaskAsync(uri)); var data = await TaskEx.WhenAll(tasks); return await TaskEx.Run(() => { return data.Sum(s => s.Length); }); }
在AsyncCTPLibrary.dll中,微软为一些类提供了扩展,如下:
WebClient的扩展如下:
可以看到基本上为每个Download 都增加了一个XXXTaskAsync 的扩展方法。
返回的全部都是Task,
为什么全部都是Task?,因为await 只能wait Task,并且await 只能用在async 标记的方法中,
async 关键字表明这是个异步方法。
第一句:
public async Task<int> SumPageSizesAsync(IList<Uri> uris)
因为我们申明的是一个异步方法,所以要使用async 关键字,SumPageSizesAsync方法返回的结果是int类型,所以返回Task<int>.
第二句:
IEnumerable<Task<Byte[]>> tasks = uris.Select(uri => new WebClient().DownloadDataTaskAsync(uri));
获取DownloadDataTaskAsync返回的所有Task。
第三句:
byte[][] data = await TaskEx.WhenAll(tasks);
首先第二句返回的是IEnumerable<Task<Byte[]>> 类型,也就是一个一个的Task<Byte[]> 的任务,使用TaskEx的WhenAll方法可以将这些任务转变成一个Task<Byte[][]> 的任务
使用await关键字意味着Task<Byte[][]> 方法需要等待,等待结束后返回Byte[][]。
第四句:
return await TaskEx.Run<int>(() =>
{
return data.Sum(s => s.Length);
});
TaskEx.Run 返回将使用第三句返回的data,将Byte[][] 的数据进行Sum运算,返回一个Task<int> 的对象,如果不使用await 的话:
因为 async 关键字代表的是异步方法,并且该异步方法返回的结果是int,所以需要再次使用await 关键字:
return await TaskEx.Run<int>(() =>
{
return data.Sum(s => s.Length);
});
修改Main代码如下:
public static void Main() { List<Uri> uris = new List<Uri>(); uris.Add(new Uri("http://localhost:57815/AsyncTestPages/QuickPage.aspx")); uris.Add(new Uri("http://localhost:57815/AsyncTestPages/SlowPage.aspx")); uris.Add(new Uri("http://localhost:57815/AsyncTestPages/VerySlowPage.aspx")); uris.Add(new Uri("http://localhost:57815/AsyncTestPages/QuickPage.aspx")); uris.Add(new Uri("http://localhost:57815/AsyncTestPages/SlowPage.aspx")); uris.Add(new Uri("http://localhost:57815/AsyncTestPages/VerySlowPage.aspx")); AsyncDemo asyncDemo = new AsyncDemo(); Console.WriteLine(DateTime.Now); int totalSize = asyncDemo.SumPageSizesAsync(uris).Result; Console.WriteLine("TotalSize:{0}, Finished", totalSize); Console.WriteLine(DateTime.Now); }
运行结果如下:
可以看到使用了16秒的时间,大致等于理论值15.
有的同学会说,很麻烦!,的确,我也感觉很麻烦,还不如ThreadPool 来的快,不过async,await主要并不是解决这类问题的,它所解决的是异步中的同步,也就是说在某些异步操作中,需要同步的去处理,比如在Silverlight中,
异步获取A –> 异步获取B –> 异步获取C..
如果使用传统的方式则需要:
WebClient webClient = new WebClient(); webClient.DownloadDataCompleted += (s, e) => { // 使用A对象,做些事情。 WebClient webClient2 = new WebClient(); webClient2.DownloadDataCompleted += (s2, e2) => { //使用B对象,做些事情。 }; webClient2.DownloadDataAsync(new Uri("B 的地址")); }; webClient.DownloadDataAsync(new Uri("A 的地址"));
当然在这里演示的是最丑陋的版本,聪明的同学可以使用Enumerable 来简化异步操作。
如果使用async 和await则可以修改为:
public async Task<int> SumPageSizesAsync3(IList<Uri> uris) { int total = 0; foreach (var uri in uris) { WebClient webClient=new WebClient(); var data = await webClient.DownloadDataTaskAsync(uri); total += data.Length; } return total; }
下面是我写的一个winPhone小程序用来实现序列化(serialize)和反序列化(deserialize):
<StackPanel Margin="0,50,0,0"> <Button x:Name="writeButton" Content="write" Click="writeButton_Click"/> <Button x:Name="readButton" Content="read" Click="readButton_Click"/> <TextBlock x:Name="resultTextBlock" FontSize="24" Height="400" TextWrapping="Wrap"/> </StackPanel>
这里简单的设置了两个按键。
Car类存储数据:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace App7 { public class Car { public int ID { set; get; } public string Make { set; get; } public string Model { set; get; } public int year { set; get; } } }
接下来就是主页中的cs文件:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices.WindowsRuntime; using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.Threading.Tasks; using Windows.Foundation; using Windows.Foundation.Collections; using Windows.Storage; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Navigation; // “空白页”项模板在 http://go.microsoft.com/fwlink/?LinkId=391641 上有介绍 namespace App7 { /// <summary> /// 可用于自身或导航至 Frame 内部的空白页。 /// </summary> public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); this.NavigationCacheMode = NavigationCacheMode.Required; } /// <summary> /// 在此页将要在 Frame 中显示时进行调用。 /// </summary> /// <param name="e">描述如何访问此页的事件数据。 /// 此参数通常用于配置页。</param> protected override void OnNavigatedTo(NavigationEventArgs e) { // TODO: 准备此处显示的页面。 // TODO: 如果您的应用程序包含多个页面,请确保 // 通过注册以下事件来处理硬件“后退”按钮: // Windows.Phone.UI.Input.HardwareButtons.BackPressed 事件。 // 如果使用由某些模板提供的 NavigationHelper, // 则系统会为您处理该事件。 } private const string XMLFILENAME = "data.xml"; private const string JSONFILENAME = "data.json"; private async void writeButton_Click(object sender, RoutedEventArgs e) {
//await writeXMLAsync(); await writeJsonAsync(); } private async void readButton_Click(object sender, RoutedEventArgs e) {
//await writeXMLAsync();
//await writeJsonAsync(); await deserilizeJsonAsync(); } private List<Car> buildObjectGraph() {
//测试数据 var myCars = new List<Car>(); myCars.Add(new Car() { ID = 1, Make = "Oldsmobile", Model = "Cutlas Superme", year = 1985 }); myCars.Add(new Car() { ID = 2, Make = "Geo", Model = "Prism", year = 1993 }); myCars.Add(new Car() { ID = 3, Make = "Ford", Model = "Escape", year = 2005 }); return myCars; } private async Task writeXMLAsync() {XML序列化 var myCars = buildObjectGraph(); var serializer = new DataContractSerializer(typeof(List<Car>)); using (var stream = await ApplicationData.Current.LocalFolder.OpenStreamForWriteAsync( XMLFILENAME, CreationCollisionOption.ReplaceExisting )) { serializer.WriteObject(stream, myCars); } resultTextBlock.Text = "Write Succeed"; } private async Task readXMLAsync() {将XML序列化文件显示在屏幕上 string content = string.Empty; var myStream = await ApplicationData.Current.LocalFolder.OpenStreamForReadAsync(XMLFILENAME); using (StreamReader reader = new StreamReader(myStream)) { content = await reader.ReadToEndAsync(); } resultTextBlock.Text = content; } private async Task writeJsonAsync() {//json序列化方法 var myCars = buildObjectGraph(); var serializer = new DataContractJsonSerializer(typeof(List<Car>)); using (var stream = await ApplicationData.Current.LocalFolder.OpenStreamForWriteAsync( JSONFILENAME, CreationCollisionOption.ReplaceExisting )) { serializer.WriteObject(stream, myCars); } resultTextBlock.Text = "Write Succeed"; } private async Task readJsonAsync() {//将序列化后的json显示在屏幕上 string content = string.Empty; var myStream = await ApplicationData.Current.LocalFolder.OpenStreamForReadAsync(JSONFILENAME); using (StreamReader reader = new StreamReader(myStream)) { content = await reader.ReadToEndAsync(); } resultTextBlock.Text = content; } private async Task deserilizeJsonAsync() {json的反序列化方法 string contect = string.Empty; List<Car> myCars; var jsonSerializer = new DataContractJsonSerializer(typeof(List<Car>)); var myStream = await ApplicationData.Current.LocalFolder.OpenStreamForReadAsync(JSONFILENAME); myCars = (List<Car>)jsonSerializer.ReadObject(myStream); foreach (var car in myCars) { contect += string.Format("ID : {0},Make : {1},Model : {2}...\n", car.ID, car.Make, car.Model); } resultTextBlock.Text = contect; }
private async Task deserilizeXMLAsync()
{//XML反序列化方法
string contect = string.Empty;
List<Car> myCars;
var XMLSerializer = new DataContractSerializer(typeof(List<Car>));
var myStream = await ApplicationData.Current.LocalFolder.OpenStreamForReadAsync(XMLFILENAME);
myCars = (List<Car>)XMLSerializer.ReadObject(myStream);
foreach (var car in myCars)
{
contect += string.Format("ID : {0},Make : {1},Model : {2}...\n", car.ID, car.Make, car.Model);
}
resultTextBlock.Text = contect;
} } }