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。

时间: 2024-08-27 09:27:14

C#异步编程一的相关文章

Javascript异步编程之setTimeout与setInterval详解分析(一)

Javascript异步编程之setTimeout与setInterval 在谈到异步编程时,本人最主要会从以下三个方面来总结异步编程( 注意: 特别解释:是总结,本人也是菜鸟,所以总结不好的,请各位大牛多多原谅!) 1. setTimeout与setInterval详细分析基本原理. 接下来这篇博客会总结setTimeout和setInterval基本点,对于上面三点会分三篇博客分别来总结,对于知道上面三点的人,但是又不是非常了解全面知识点的码农来说,没有关系的,我们可以慢慢来学习,来理解,或

net异步编程之await

net异步编程之await 初探asp.net异步编程之await 终于毕业了,也顺利进入一家期望的旅游互联网公司.27号入职.放肆了一个多月没写代码,好方啊. 另外一下观点均主要针对于await. 写在前面(带着问题学习) 一.根据代码和执行结果,初探异步编程的执行过程. *问题1:await会让当前线程一直等待吗?await等待的时间中一直占用线程资源吗? *问题2:等待await数据返回交给等待线程再继续向下执行吗? *问题3:向await下一条语句执行的线程,是执行await的线程吗?

ASP.NET Core Web 应用程序系列(四)- ASP.NET Core 异步编程之async await

原文:ASP.NET Core Web 应用程序系列(四)- ASP.NET Core 异步编程之async await PS:异步编程的本质就是新开任务线程来处理. 约定:异步的方法名均以Async结尾. 实际上呢,异步编程就是通过Task.Run()来实现的. 了解线程的人都知道,新开一个线程来处理事务这个很常见,但是在以往是没办法接收线程里面返回的值的.所以这时候就该await出场了,await从字面意思不难理解,就是等待的意思. 执行await的方法必须是async修饰的,并且是Task

Javascript异步编程之setTimeout与setInterval详解

http://www.cnblogs.com/tugenhua0707/ 在谈到异步编程时,本人最主要会从以下三个方面来总结异步编程( 注意: 特别解释:是总结,本人也是菜鸟,所以总结不好的,请各位大牛多多原谅!) 1. setTimeout与setInterval详细分析基本原理. 接下来这篇博客会总结setTimeout和setInterval基本点,对于上面三点会分三篇博客分别来总结,对于知道上面三点的人,但是又不是非常了解全面知识点的码农来说,没有关系的,我们可以慢慢来学习,来理解,或者

异步编程之Javascript Promises 规范介绍

什么是 Promises Promises是一种关于异步编程的规范,目的是将异步处理对象和处理规则进行规范化,为异步编程提供统一接口. 传统的回调函数 说到JavaScript的异步编程处理,通常我们会想到回调函数,如下面的代码: getFileAsync("1.txt", function(error, result){      if(error){          throw error;      }     // 取得成功时的处理 }); 上面的代码定义了一个获取文件内容的

异步编程之promise

是ES6中新增的异步编程解决方案. 1.构造函数. 可以通过promise构造函数来实例化. 数据: const imgs = [ 'https://img.alicdn.com/tps/i4/TB1fD77gfuSBuNkHFqDSutfhVXa.jpg_240x240q90.jpg', 'https://img.alicdn.com/simba/img/TB1CYyRdAUmBKNjSZFOSuub2XXa.jpg', 'https://img.alicdn.com/imgextra/i3/

ECMA Script 6_异步编程之 Promise

Promise 对象 异步编程 方案,已同步的方式表达异步的代码,解决回调地狱的问题 比传统的解决方案——回调函数和事件——更合理和更强大 是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果. 从语法上说,Promise 是一个对象,从它可以获取异步操作的消息. Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理 基本使用 const promise = new Promise(function(resolve, reject) { // ... s

异步编程之Async,Await和ConfigureAwait的关系

在.NET Framework 4.5中,async / await关键字已添加到该版本中,简化多线程操作,以使异步编程更易于使用.为了最大化利用资源而不挂起UI,你应该尽可能地尝试使用异步编程.虽然async / await让异步编程更简单,但是有一些你可能不知道的细节和注意的地方  新关键字 微软在.NET框架中添加了async和await关键字.但是,使用它们,方法的返回类型应为Task类型.(我们将在稍后讨论例外情况)为了使用await关键字,您必须在方法定义中使用async.如果你在方

Python笔记6#面向对象高级编程一

▲__slots__ 当定义一个class并创建了一个class的实例之后,我们可以给该实例绑定任何属性和方法.这就是动态语言的灵活性. 实例代码如下: #定义一个类Student >>> class Student(object): ... pass ... #创建类Student的一个实例stu1 >>> stu1 = Student() #给实例绑定一个属性name >>> stu1.name = 'michael' >>> s