C#异步

一、简介

  首先来看看.net的发展中的各个阶段的特性:NET 与C# 的每个版本发布都是有一个“主题”。即:C#1.0托管代码→C#2.0泛型→C#3.0LINQ→C#4.0动态语言→C#4.5异步编程

  下面我来简单的介绍一下异步编程:异步编程,在 .NET Framework 4.5 和 Windows 运行时利用异步支持。 编译器可执行开发人员曾进行的高难度工作,且应用程序保留了一个类似于同步代码的逻辑结构。 因此,你只需做一小部分工作就可以获得异步编程的所有好处。

  所谓的异步编程是利用CPU空闲时间和多核的特性,它所返回的Task或Task<TResult>是对await的一个承诺,当任务执行完毕后返回一个结果给接收者。这里看到这个可能各位不太明白,不要紧,下面会有讲解。

二、使用说明

方法签名包含一个 Async 或 async 修饰符。

按照约定,异步方法的名称以“Async”后缀结尾。

返回类型为下列类型之一:

如果你的方法有操作数为 TResult 类型的返回语句,则为 Task<TResult>。

如果你的方法没有返回语句或具有没有操作数的返回语句,则为 Task。

如果你编写的是异步事件处理程序,则为 Void(Visual Basic 中为 Sub)。

有关详细信息,请参见本主题后面的“返回类型和参数”。

方法通常包含至少一个 await 表达式,该表达式标记一个点,在该点上,直到等待的异步操作完成方法才能继续。 同时,将方法挂起,并且控件返回到方法的调用方。(这里所谓的挂起就是上文所提到的承诺,异步方法承诺会给调用方一个结果)

三、示例

  实践才是检验真知的最佳途径。

using System;

using System.Diagnostics;

using System.Net.Http;

using System.Threading.Tasks;

namespace 异步递归

{

class Program

{

static void Main(string[] args)

{

Stopwatch stopwatch = new Stopwatch();

stopwatch.Start();

ConsoleAsync1();

stopwatch.Stop();

Console.WriteLine("同步方法用时:" + stopwatch.ElapsedMilliseconds);

stopwatch.Reset();

stopwatch.Start();

ConsoleAsync();

stopwatch.Stop();

Console.WriteLine("异步方法用时:"+ stopwatch.ElapsedMilliseconds);

Console.Read();

}

private static async void ConsoleAsync()

{

Console.WriteLine("异步方法开始");

Console.WriteLine("Result:" + await SumAsync(10));

Console.WriteLine("异步方法结束");

}

private static async Task<int> SumAsync(int part)

{

if ((part += 10) >= 100)

{

return 100;

}

HttpClient client = new HttpClient();

Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");

Console.WriteLine(DateTime.Now.Millisecond + " 异步 " + (await getStringTask).Length);

return await SumAsync(part);

}

private static void ConsoleAsync1()

{

Console.WriteLine("同步方法开始");

Console.WriteLine("Result:" + SumAsync1(10));

Console.WriteLine("同步方法结束");

}

private static int SumAsync1(int part)

{

if ((part += 10) >= 100)

{

return 100;

}

HttpClient client = new HttpClient();

Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");

Console.WriteLine(DateTime.Now.Millisecond + " 同步 " + getStringTask.Result.Length);

return SumAsync1(part);

}

}

}

  示例介绍:

    1、这个例子中有两种实现方式:(1)利用异步编程的方式实现(2)利用普通同步方式实现

    2、同时这个例子中实现了递归,这个可以不用考虑,博主只是想验证一下在异步的情况下,递归是否有效而已,实验结果为有效。

    3、这段代码中的GetStringAsync()方法是获取远程界面内容用的,主要目的是延长响应时间。

  在使用多线程编写端口扫描程序时,我自己感觉同步和确定所有线程都执行完的时间是2个比较麻烦的问题。有园友评论说现在已经不手动创建thread对象了,而是直接使用Task异步方式,我的网络编程老师也讲到了异步编程的优越性。在学习了课本上的知识后,进行了一个总结分享给大家。从.NET4.5开始,用async和await关键字再加上Task.Run是一个非常不错的异步编程模型。

1.await和async

  异步模式从技术上看就是利用委托来实现的,它的主要好处是在异步执行的过程中,用户仍然可以操控UI界面。使用Task类和使用Thread类有很多相似的地方,Task类也是通过调用方法去实现一个任务的完成,方法可是是命名方法或匿名方法,在执行过程中可使用async和await来实现异步执行。async是一个修饰符,它只能用在方法或者事件处理程序的签名中。对于方法可分为有返回值和无返回值两种情况,事件则只有一种,如下面三条语句所示:

    private async Task<int> MethodAsync();//有返回值的异步方法

    private async Task MethodAsync();//无返回值的异步方法

    private async void btnOk_Click();//异步事件处理程序

  await是一个运算符,它表示等待异步执行的结果。也可以理解为await运算符实际上是对方法的返回值进行操作,也就是对Task<Result>进行操作,而不是对方法本身进行操作。还有一点要注意,await是一定要放在异步方法的内部,如果没有放在内部的话,VS会自动报错。以下是async和await使用的例子: 

    private async void button5_Click(object sender, EventArgs e)
    {
      Task a = Method1Async();
      //此处可继续执行其他代码
      await a;//等待任务a完成
      Task<int> b = Method2Async();
      //此处可继续执行其他代码
      int c = await b;//等待任务b完成,且可以拿到任务b的返回值
    }

    Task Method1Async();
    async Task<int> Method2Async()
    {
      await Task.Delay(100);
      return 1;
    }

  await和同步编程最大的不同之处是:异步等待任务完成的时候,在不会继续执行后面的代码时,也不会影响界面的操作。在.NET提供的类中,异步方法都是约定用Async作为后缀,这样可以很清楚的知道这个方法是异步方法还是同步方法。

2. 创建任务

  创建任务也就是将任务与要执行的方法联系起来,编写任务执行的方法时,这个方法既可以是同步方法也可以是异步方法,还可以是匿名方法。执行异步方法时,必须用async和Task共同表示没有返回值的任务,用async和Task<TResult>共同表示返回值为TResult的任务。以下是定义执行任务的方法。

    private async void button5_Click(object sender, EventArgs e)
    {

      //Task.Run方法表示使用默认的任务调度程序在线程池中通过后台执行指定的任务

      //如果不需要自己去调度方法,使用这个方式最方便
      await Task.Run(()=>Method1Async());//执行同步方法
      int c = await Task.Run(()=>Method2Async());//执行异步方法
      await Task.Run(async () => { c = 2; });//执行异步匿名方法
    }
    void Method1Async();
    async Task<int> Method2Async(){...}

  Task.Run方法常用的重载形式有以下4种,另外它也是可以用new关键字显示创建任务,但是这种方式用的不多。

    Task Run(Func<Task> function);//执行不带返回值的任务

    Task<TResult> Run<TResult>(Func<Task<TResult>> function);//执行带返回值的任务

    Task<TResult> Run<TResult>(Func<Task<TResult>> function, CancellationToken cancellationToken);//执行过程中可以监听取消通知

    Task Run(Func<Task> function, CancellationToken cancellationToken);//执行过程中可以监听取消通知

3. 终止任务

  在执行任务时肯定会出现需要终止任务的情况,这里的终止告诉任务你要尽快停下来不再执行了,而不是直接销毁任务实例。这里可以打个比方,学生一起出去吃饭了,学生与老师都在班群里面,突然班群里老师说要让同学们集合,如果所有同学都看到这个消息,然后学生们开始出发,这样就可以正确的集合。上面的例子有一个很重要的前提,那就是所有同学都要看到这个消息,也就是学生是时刻监听消息的。CancellationTokenSource类和CancellationToken结构用于实现多线程、线程池和Task任务的取消操作,处理模式与上面的例子相似。创建的班群就是CancellationTokenSource对象,收到的通知就是CancellationToken对象。CancellationTokenSource用于创建取消通知,CancellationToken则用于传播应取消操作的通知,当调用任务前,可以先创建取消源对象CancellationTokenSource cts=new CancellationTokenSource();,如果希望在30秒后自动发出取消通知,可以传入参数CancellationTokenSource(TimeSpan.FromSeconds(30));CancellationToken ct=cts.Token;,后一句代码是拿到取消的通知。CancellationTokenSource还有一个Cancel方法,将这个属性设为true时,该方法会将所有添加了取消标记的CancellationToken对象的IsCancellationRequested属性都设置为true,这样取消通知就传递到了正在执行的任务。

  任务收到取消通知后,可以选择两种方式来终止操作。第一种方式是简单的从委托返回。这种实现方式类似于在调用任务的代码中一个bool值来表示取消通知,任务收到后就直接返回了。当采用这种方式时任务状态的返回值为TaskStatus.RanToCompletion枚举值,它表示正常完成,而不是TaskStatus.Canceled枚举值。第二种方式是在代码里引发OperationCanceledException异常,并将其传递到在其上请求了取消的标记,采用这种方式取消的任务会转换为用Canceled枚举值表示的状态。完成引发异常的首选方式是调用ct.ThrowIfCancellationRequestes();。以下是代码示例,写了一个winform程序,利用进度条来取消任务。第一个图是没有引发异常时,程序退出for循环,执行后面的代码后返回了,第二张图是第二种方式,引发了异常后直接跳转到catch语句块了。

     CancellationTokenSource cts;
        private async void button3_Click(object sender, EventArgs e)
        {
            progressBar1.Maximum = 100;
            progressBar1.Value = 0;
            cts = new CancellationTokenSource();
            var aa = MYThreadAsync("a", cts.Token);
            try
            {
                await aa;
                listBox1.Items.Add("await后面");
            }
            catch
            {
                if (aa.IsCanceled)
                    listBox1.Items.Add("a取消");
            }
        }

        private void button4_Click(object sender, EventArgs e)
        {
            cts.Cancel();
        }

        public async Task MYThreadAsync(string s, CancellationToken ct)
        {
            for (int i = 0; i < 50; i++)
            {
                if (ct.IsCancellationRequested)
                    break;          //点击关闭按钮,IsCancellationRequested就为true,就会退出for循环,这是第一种方式
                progressBar1.Value += 2;
                await Task.Delay(100);
                ct.ThrowIfCancellationRequested();//这是第二种方式,它会终止任务并且返回catch语句块里面
            }
            listBox1.Items.Add("任务" + s + "完成了");
        }

4. 获取任务执行的状态

  在异步编程中,很显然任务执行的状态是一个非常重要的参数。在任务的生命周期里,可以通过Status属性来获取任务执行的状态,当任务完成后还可以通过任务属性知道任务完成的情况。可利用任务实例的Status属性获取任务执行的状态,任务执行的状态用TaskStatus枚举表示,以下是TaskStatus的枚举值:

Created:任务已经初始化,但尚未进入调度计划

WaitingForActivation:该任务已进入调度计划,正在等待被调度程序激活

WaitingToRun:该任务已被调度程序激活,但尚未开始执行

Running:该任务正在运行,但尚未完成

RanToCompletion:该任务已经成功完成

Canceled:该任务由于被取消而完成,引发异常或调用方已向该任务的CancellationToken发出信号

Faulted:该任务因为出现未经处理的异常而完成

WaitingForChildrenToComplete:该任务本身已完成,正等待附加的子任务完成

  任务完成情况相关的属性有IsCompleted、IsCanceled和IsFaulted等属性,从单词意思上看不难理解它们的意思,其中要注意IsCompleted属性表示任务是否完成,无论是正常结束还是因为取消或异常而完成都为完成。

5. 任务执行的进度

  有时候我们希望让某些异步操作提供进度通知,以便在界面中显示异步操作执行的进度,可以用Progress<T>类来得到任务执行的进度。以下是利用方法里的Report方法将方法内变量的值传回创建任务的事件代码里,从而更新进度条的值。

     CancellationTokenSource cts;
        private async void button3_Click(object sender, EventArgs e)
        {
            progressBar1.Maximum = 100;
            progressBar1.Value = 0;
            cts = new CancellationTokenSource();
            CancellationToken ct = cts.Token;
            var pp = new Progress<int>();
            pp.ProgressChanged += (s, n) => {
                progressBar1.Value = n;
            };
            var tt = Task.Run(()=>MYThreadAsync(pp,cts.Token,500),cts.Token);
            try
            {
                await tt;
                if (tt.Exception == null)
                    listBox1.Items.Add("任务完成");
            }
            catch (Exception ex)
            {
                listBox1.Items.Add("异常" + ex.Message);
            }
        }
        private void button4_Click(object sender, EventArgs e)
        {
            cts.Cancel();
        }
        public  void MYThreadAsync(IProgress<int> progress, CancellationToken ct, int delay)
        {
            int p = 0;//进度
            while (p < 100 && ct.IsCancellationRequested == false)
            {
                p += 1;
                Thread.Sleep(delay);
                progress.Report(p);//这个方法将会触发ProgressChanged事件更新进度条
            }
        }

6. 定时完成任务

  无论是服务器还是客户端,都是有定时完成某个任务的需要的。System.Timers.Timer类是一个不错的定时设置类,这个类可以引发事件,但它默认是在线程池中引发事件,而不是在当前线程中引发事件。Timer类的常用属性有AutoReset和Interval属性,AutoReset是获取或设置一个bool值,该值为true表示每次间隔结束时都引发一次Elapsed事件,false表示仅在首次间隔结束时引发一次该事件。Interval属性是获取或设置两次Elapsed事件的间隔时间,该值必须大于零并小于Int.MaxValue,默认值为100毫秒。Timer类还有两个常用方法那就是Start和Stop方法。

  还有一个System.Threading.Timer类,它也是在线程池中定时执行任务,它与前一个Timer类的区别是该类不使用事件模型,而是直接通过TimerCallback类型的委托来实现的。该类的构造函数为:Timer(TimerCallback callback,Object state,TimeSpan douTime,TimeSpan period)。callback表示要执行的方法,state表示一个包含回调方法要使用的信息的对象,dueTime是首次调用回调方法之前延迟的时间,period表示每次调用回调方法的时间间隔,-1表示终止。这样创建对象后,首次到达dueTime延时时间会自动调用一次callback委托,以后每隔period时间间隔调用一次。以下是这两种方式的运行效果和源代码。

     System.Timers.Timer timer;
        System.Threading.Timer threadtimer;
        private void button2_Click(object sender, EventArgs e)//Timers.Timer
        {
            progressBar1.Maximum = 100;
            progressBar1.Value = 0;
            int pro=0;
            timer = new System.Timers.Timer(500);
            timer.AutoReset = true;
            timer.Elapsed+= (obj, args) =>
            {
                pro+=5;
                progressBar1.Value = pro;
            };
            timer.Start();
        }

       private void button5_Click(object sender, EventArgs e)
       {
           timer.Stop();
           listBox1.Items.Add("第一个已经停止");
       }
    //Threading.Timer类
       private void button1_Click(object sender, EventArgs e)
       {
           progressBar2.Maximum = 100;
           progressBar2.Value = 0;
           TimeSpan dueTime = new TimeSpan(0, 0, 0, 1);
           TimeSpan period = new TimeSpan(0, 0, 0, 0, 200);
           System.Threading.TimerCallback timecall = new TimerCallback((obj) => progressBar2.Value += 5);
           threadtimer = new System.Threading.Timer(timecall, null, dueTime, period);
       }

       private void button6_Click(object sender, EventArgs e)
       {
           threadtimer.Dispose();
           listBox1.Items.Add("第二个已经停止");
       }

  这篇文章只总结了单个任务的异步执行的基础,还得继续学习多任务并行执行。如果有更好的技术或者与企业使用相关的异步技术,希望园友可以提出我继续学习。

时间: 2025-01-16 14:53:54

C#异步的相关文章

cocos2dx lua中异步加载网络图片,可用于显示微信头像

最近在做一个棋牌项目,脚本语言用的lua,登录需要使用微信登录,用户头像用微信账户的头像,微信接口返回的头像是一个url,那么遇到的一个问题就是如何在lua中异步加载这个头像,先在引擎源码里找了下可能会提供这个功能的地方,发现好像没有提供类似功能,那么只能自己动手写.所以我在ImageView这个类里面添加了一个成员方法,其实可以不写在ImageView里,而且我觉得非必需情况下还是不要修改引擎源码的好,因为如果源码改动比较多的话,将来引擎版本升级会比较麻烦.我写在ImageView里纯粹是想偷

学习笔记12JS异步请求

*一般用JS来监听按钮事件,都应该先监听页面OnLoad事件. *Js写在哪里,就会在页面解析到哪里执行. 异步请求:所谓异步请求,就是使用JS来监听按钮点击事件,并且发送请求,等到回复后,再使用JS来进行页面跳转,或动态改变页面.使用场合:当请求是ashx是,都可以使用异步方法,页面就无需刷到ashx的一个空白页面或者不用于展示的页面了. *使用jquery发送异步请求:$("#按钮ID").Click(fuction(){ $.get( "页面URL.ashx"

同步异步中的一致性

简述一致性中关于同步与异步环境下的共识理论 (##转载请注明) 共识问题:可称作协作,所有正确的进程对提议的值达成一致.分布式系统中,节点之间通过通信,对请求达成一致的定序. 问题定义:进程Pi处于未决状态(undecideed),提议集合D中的某个值Vi.进程之间相互通信,交换各自的提议.每个进程设置自己的决定变量(decision variable),进入决定状态(decided),此状态下不改变di的数值. 要求满足如下几个性质: 终止性(Termination):正确的进程最终都可以设置

信号驱动和异步驱动的区别

5种I/O模型: 1.阻塞I/O 2.非阻塞I/O 3.异步I/O 4.信号驱动I/O 5.I/O复用 信号驱动和异步驱动的区别 信号驱动IO是指:进程预先告知内核,使得 当某个socketfd有events(事件)发生时,内核使用信号通知相关进程. 异步IO(Asynchronous IO)是指:进程执行IO系统调用(read / write)告知内核启动某个IO操作,内核启动IO操作后立即返回到进程.IO操作即内核当中的服务例程. 异步I/O和信号驱动I/O的区别很容易被混淆.前者与后者的区

java中同步和异步有什么异同?

同步交互:指发送一个请求,需要等待返回,然后才能够发送下一个请求,有个等待过程: 异步交互:指发送一个请求,不需要等待返回,随时可以再发送下一个请求,即不需要等待. 区别:一个需要等待,一个不需要等待,在部分情况下,我们的项目开发中都会优先选择不需要等待的异步交互方式. 哪些情况建议使用同步交互呢?比如银行的转账系统,对数据库的保存操作等等,都会使用同步交互操作,其余情况都优先使用异步交互.

019-JQuery(Ajax异步请求)

使用jquery完成异步操作 ->开发文档提供的异步API url:请求地址 type:请求方式,主要是get.post data:{}:请求的数据 dataType:返回值的类型,主要有xml.text.json.script.html success:function(data){...}成功的回调函数(4,200) GetTime.html 1 <!DOCTYPE html> 2 <html xmlns="http://www.w3.org/1999/xhtml&q

异步处理未登录url跳转

http://localhost:8080/xxx/login?url=http://localhost:8080/xxx/oldurl // 从url中获取原页面url=http://localhost:8080/xxx/oldurl function GetQueryString(paras){ var url = location.href; var paraString = url.substring(url.indexOf("?")+1,url.length).split(&

生产者消费者模型实现多线程异步交互

[Python之旅]第六篇(五):生产者消费者模型实现多线程异步交互 消息队列 生产者消费者模型 多线程异步交互 摘要:  虽然标题是"生产者消费者模型实现多线程异步交互",但这里要说的应该还包括Python的消息队列,因为这里多线程异步交互是通过Python的消息队列来实现的,因此主要内容如下: 1 2 3 4 1.生产者消费者模型:厨师做包子与顾客吃包子 2.Python的消息队列 3.利用... 虽然标题是"生产者消费者模型实现多线程异步交互",但这里要说的应

Atitit.异步编程技术原理与实践attilax总结

1. 俩种实现模式 类库方式,以及语言方式,java futuretask ,c# await1 2. 事件(中断)机制1 3. Await 模式(推荐)1 4. Java的实现模式futuretask 对于c#的task类库(推荐)1 4.1. 使用Java 8的CompletableFuture实现函数式的回调.htm2 5. Timer模式2 6. Thread模式(不推荐,太底层)2 1. 俩种实现模式 类库方式,以及语言方式,java futuretask ,c# await 2. 事

通过Jquery异步获取股票实时数据

最近朋友让我帮他做个异步获取数据的程序,暂时服务器什么都没有,所以我就想先拿股票数据打个框架,方便后续开发和移植等事情 代码如下: <!-- 说明:股票看盘 作者:黑桃A 时间:2014-04-14 参考: http://www.lxway.com/240649562.htm http://www.lxway.com/946486042.htm http://www.365mini.com/page/jquery_getscript.htm http://blog.csdn.net/xxjoy_