C#异步编程一
Posted 小崔笔记本
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#异步编程一相关的知识,希望对你有一定的参考价值。
前几天把Code First系列总结完,想着下步总结什么,原本想着XML,不过XML的内容比较多,还有3天班就中秋节了,想在中秋节前在完成一个系列,所以决定把异步这块总结下。说起异步可能会认为就是多线程,其实并不是这样,多线程之一解决异步的一种方式,单线程也可以实现异步。C#中异步主要包括异步委托和异步任务。今天总结下异步委托。
一、同步问题
大家小时候都可能听说过小明上学的故事,小明起床要洗脸、烧水、做饭、跑步、吃饭,今天来的例子也还是小明,不过不是上学而是睡觉。小明睡觉之前要洗澡洗干净了。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace AsyncDemo { public class Heater { /// <summary> /// 设定的温度 /// </summary> public int SetTemp { get; set; } /// <summary> /// 当前水温 /// </summary> private int _currentTemp; public int CurrentTemp { get { return _currentTemp; } } private bool _flag; public bool Flag { get { return _flag; } } public Heater() { this._flag = false; } /// <summary> /// 烧水 /// </summary> public int BoilWater() { for (int i = 0; i <= 100; i++) { Thread.Sleep(100); _currentTemp = i; Console.WriteLine("当前温度{0}",_currentTemp); if (_currentTemp >= SetTemp) { _flag = true; break; } } return _currentTemp; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AsyncDemo { public class XiaoMing { public Heater heater { get; set; } public XiaoMing(Heater heater) { this.heater = heater; } public int OpenHeater() { Console.WriteLine("打开热水器,开始烧水"); return heater.BoilWater(); } public void WatchTv() { Console.WriteLine("看电视中"); } } }
static void Main(string[] args) { Heater heater = new Heater(); heater.SetTemp = 20; XiaoMing xiaoMing = new XiaoMing(heater); xiaoMing.OpenHeater(); xiaoMing.WatchTv(); if (xiaoMing.heater.Flag) { Console.WriteLine("水烧好了"); } Console.ReadLine(); }
小明想着烧着水闲着也是闲着,不能开着热水器吧,他想看电视,看片。但是看下面的输入让小明失望了。
小明傻眼了,我这要看着烧水器的温度,烧好了,才能看电视,这不行啊,还要上床呢。看电视只是在烧水的时候看会。这要是在烧水的时候我能看会电视多好。
二、异步的引入
上面也看到了,烧水和看电视不能同时进行,那怎么能同时进行,烧水又不是小明烧,是热水器烧,自己只是打开热水器就好了。为了解决这个问题,C#中有了异步委托。
首先引用MSDN中的一段话来描述一下如何使用异步方式
.NET Framework 允许您异步调用任何方法。 为此,应定义与您要调用的方法具有相同签名的委托;公共语言运行时会自动使用适当的签名为该委托定义 BeginInvoke 和 EndInvoke 方法。
BeginInvoke 方法启动异步调用。 该方法与您需要异步执行的方法具有相同的参数,还有另外两个可选参数。 第一个参数是一个 AsyncCallback 委托,该委托引用在异步调用完成时要调用的方法。 第二个参数是一个用户定义的对象,该对象将信息传递到回调方法。 BeginInvoke 立即返回,不等待异步调用完成。 BeginInvoke 返回一个 IAsyncResult,后者可用于监视异步调用的进度。
EndInvoke 方法检索异步调用的结果。 在调用 BeginInvoke 之后随时可以调用该方法。 如果异步调用尚未完成,则 EndInvoke 会一直阻止调用线程,直到异步调用完成。 EndInvoke 的参数包括您需要异步执行的方法的 out 和 ref 参数(在 Visual Basic 中为 <Out> ByRef 和 ByRef)以及由 BeginInvoke 返回的 IAsyncResult。
如果上面的定义认真多读几遍,基本把异步委托精髓学到了。下面就在上面的代码基础上改变一下,让小明可以在烧水的时候可以看电视。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace AsyncDemo { public class XiaoMing { public Heater heater { get; set; } //应定义与您要调用的方法具有相同签名的委托 public delegate int BoilWaterDelegate(int orgTemp); private BoilWaterDelegate _BoilWaterDelegate { get; set; } public XiaoMing(Heater heater) { this.heater = heater; _BoilWaterDelegate = new BoilWaterDelegate(heater.BoilWater); } /// <summary> /// BeginInvoke 立即返回,不等待异步调用完成。 BeginInvoke 返回一个 IAsyncResult,后者可用于监视异步调用的进度。 /// </summary> /// <param name="orgTemp"></param> /// <param name="callBack"></param> /// <param name="obj"></param> /// <returns></returns> public IAsyncResult BeginBoilWater(int orgTemp,AsyncCallback callBack, object obj) { Console.WriteLine("打开热水器,开始烧水"); try { //BeginInvoke 函数中 回调函数前面的参数是给委托传的参数 后面是给回调函数传的参数 // 该方法与需要异步执行的方法具有相同的参数,还有另外两个可选参数,orgTemp与需要异步执行的方法具有相同的参数 //第一个参数是一个 AsyncCallback 委托,该委托引用在异步调用完成时要调用的方法 callBack就是 //第二个参数是一个用户定义的对象,该对象将信息传递到回调方法 obj就是 return _BoilWaterDelegate.BeginInvoke(orgTemp, callBack, obj); } catch (Exception ex) { throw ex; } } /// <summary> /// 烧水结束 /// </summary> /// <param name="ar"></param> /// <returns></returns> public int EndBoilWater(IAsyncResult ar) { if (ar == null) throw new NullReferenceException("IAsyncResult 参数不能为空"); try { return _BoilWaterDelegate.EndInvoke(ar); } catch (Exception e) { throw e; } } public void WatchTv() { Console.WriteLine("看电视中"); } public void Wash(IAsyncResult ir) { string someThing= ir.AsyncState as string; Console.WriteLine("拿{0}洗澡",someThing); } public void Sleep() { Console.WriteLine("上床睡觉"); } } }
为了运行上面的定义,在委托上加了一个参数设置一个起始温度,并设置了回调函数。并在回调函数Wash中使用了自定义的一个对象。注意上面的注释前面的BeginInvoke 的描述,它们是对应着的。
static void Main(string[] args) { Heater heater = new Heater(); heater.SetTemp = 60; XiaoMing xiaoMing = new XiaoMing(heater); //BeginInvoke 立即返回,不等待异步调用完成。 //WatchTv函数会在输出当前温度前面执行 //BeginInvoke 返回一个 IAsyncResult,后者可用于监视异步调用的进度 IAsyncResult ar = xiaoMing.BeginBoilWater(40, xiaoMing.Wash, "肥皂"); xiaoMing.WatchTv(); //在调用 BeginInvoke 之后随时可以调用 // 如果异步调用尚未完成,则 EndInvoke 会一直阻止调用线程,直到异步调用完成 // 如果放在EndBoilWater之后就会在输出当前温度后执行 int currentTemp = xiaoMing.EndBoilWater(ar); Console.ReadLine(); }
可以从输出结果上看到,小明开始烧水之后就去看电视了,并且水也在烧着。
从上面可以看到BeginInvoke返回IAsyncResult接口类型,那IAsyncResult到底是什么呢?
// // 摘要: // 表示异步操作的状态。 [ComVisible(true)] public interface IAsyncResult { // // 摘要: // 获取用户定义的对象,它限定或包含关于异步操作的信息。 // // 返回结果: // 用户定义的对象,它限定或包含关于异步操作的信息。 object AsyncState { get; } // // 摘要: // 获取用于等待异步操作完成的 System.Threading.WaitHandle。 // // 返回结果: // 用于等待异步操作完成的 System.Threading.WaitHandle。 WaitHandle AsyncWaitHandle { get; } // // 摘要: // 获取一个值,该值指示异步操作是否同步完成。 // // 返回结果: // 如果异步操作同步完成,则为 true;否则为 false。 bool CompletedSynchronously { get; } // // 摘要: // 获取一个值,该值指示异步操作是否已完成。 // // 返回结果: // 如果操作完成则为 true,否则为 false。 bool IsCompleted { get; } }
上面的属性可用于监视异步调用的进度。面下面的代码中使用while循环根据IsCompleted来判断异步是否完成
static void Main(string[] args) { Heater heater = new Heater(); heater.SetTemp = 60; XiaoMing xiaoMing = new XiaoMing(heater); //BeginInvoke 立即返回,不等待异步调用完成。 //WatchTv函数会在输出当前温度前面执行 //BeginInvoke 返回一个 IAsyncResult,后者可用于监视异步调用的进度 IAsyncResult ar = xiaoMing.BeginBoilWater(40, xiaoMing.Wash, "肥皂"); xiaoMing.WatchTv(); int i = 0; while (!ar.IsCompleted) { if (ar.IsCompleted) { Console.WriteLine("水烧好了"); } Console.WriteLine(i++); } //在调用 BeginInvoke 之后随时可以调用 // 如果异步调用尚未完成,则 EndInvoke 会一直阻止调用线程,直到异步调用完成 // 如果放在EndBoilWater之后就会在输出当前温度后执行 int currentTemp = xiaoMing.EndBoilWater(ar); Console.ReadLine(); }
从输出的i的值可以看到while会一直执行直到IsCompleted=true。然后使用AsyncWaitHandle来判断。
使用 IAsyncResult.AsyncWaitHandle 属性获取 WaitHandle,使用其 WaitOne 方法阻止执行,直至 WaitHandle 收到信号,然后调用 EndInvoke。其实它就是个信号量,当使用其Waitone()方法的时候,程序就会发生阻塞,如果异步完成,Waitone就会返回true,否则返回false。当然最方便的就是我们可以在Waitone中设置一个时间来做超时处理,比如我们可以在 BeginBoilWater代码后增加ar.AsyncWaitHandle.WaitOne(1000),由于,异步方法的线程需要10000ms,主线程等待了1000ms后,认为是超时了,便会继续执行后面老王看电视的代码。
static void Main(string[] args) { Heater heater = new Heater(); heater.SetTemp = 60; XiaoMing xiaoMing = new XiaoMing(heater); //BeginInvoke 立即返回,不等待异步调用完成。 //WatchTv函数会在输出当前温度前面执行 //BeginInvoke 返回一个 IAsyncResult,后者可用于监视异步调用的进度 IAsyncResult ar = xiaoMing.BeginBoilWater(40, xiaoMing.Wash, "肥皂"); ar.AsyncWaitHandle.WaitOne(1000); xiaoMing.WatchTv(); //在调用 BeginInvoke 之后随时可以调用 // 如果异步调用尚未完成,则 EndInvoke 会一直阻止调用线程,直到异步调用完成 // 如果放在EndBoilWater之后就会在输出当前温度后执行 int currentTemp = xiaoMing.EndBoilWater(ar); Console.ReadLine(); }
可以看到在WatchTv前使用AsyncWaitHandle.WaitOne(1000)以看阻塞线程1000毫秒,可以看到看电视在异步线程开始一段时间才开始执行。假如也是使用while循环来输出,和前面的IsCompleted又不一样。
static void Main(string[] args) { Heater heater = new Heater(); heater.SetTemp = 60; XiaoMing xiaoMing = new XiaoMing(heater); //BeginInvoke 立即返回,不等待异步调用完成。 //WatchTv函数会在输出当前温度前面执行 //BeginInvoke 返回一个 IAsyncResult,后者可用于监视异步调用的进度 IAsyncResult ar = xiaoMing.BeginBoilWater(40, xiaoMing.Wash, "肥皂"); xiaoMing.WatchTv(); int i = 0; bool flag = true; while (flag) { flag = !ar.AsyncWaitHandle.WaitOne(1000); Console.WriteLine(i++); } //在调用 BeginInvoke 之后随时可以调用 // 如果异步调用尚未完成,则 EndInvoke 会一直阻止调用线程,直到异步调用完成 // 如果放在EndBoilWater之后就会在输出当前温度后执行 int currentTemp = xiaoMing.EndBoilWater(ar); Console.ReadLine(); }
可以看到,输出i只执行了3次,并不像前面IsCompleted那样一直输出i,使用AsyncWaitHandle使线程阻塞,不再执行,直到信号量为true。
以上是关于C#异步编程一的主要内容,如果未能解决你的问题,请参考以下文章