一直以来很想梳理下我在开发过程中使用异步编程的心得和体会,但是由于我是APM异步编程模式的死忠,当TAP模式和TPL模式出现的时候我并未真正的去接纳这两种模式,所以导致我一直没有花太多心思去整理这两部分异步编程模型。今天在CodeProject上面偶然间看到一篇关于异步编程的文章,概括总结的非常好,省去了我自己写的麻烦,索性翻译过来,以飨各位。

在阻塞和并行编程过程中,异步和多线程是非常重要的特性。异步编程可以涉及到多线程,也可以不用涉及。如果我们能够把二者放到一起来讲解的话,我们会理解的更快一些。所以今天在这里,我主要想讲解的内容是:

  1. 异步编程
  2. 是否需要多线程
  3. TAP编程模型
  4. 并行编程

首先来说明异步编程

所谓的异步编程就是指 当前的操作独立于主操作流程。在C#编程中,我们总是从进入Main方法开始 ,Main方法返回结束。在开始和结束之间的所有操作,都会挨个的执行,下一个操作必须等到上一个操作完毕才开始执行,我们看看如下代码:

  1. static void Main(string[] args)
  2. {
  3. DoTaskOne();
  4. DoTaskTwo();
  5. }

“DoTaskOne”方法必须在“DoTaskTwo”方法之前执行。也就是发生了阻塞现象。

但是在异步编程中,方法的调用并不会阻塞主线程。当方法被调用后,主线程仍会继续执行其他的任务。这种情形,一般都是使用Thread或者是Task来实现的。

在上面的场景中,如果我们让方法“DoTaskOne”异步执行,那么主线程将会立即返回,然后执行“DoTaskTwo”方法。

在.NET中,我们可以利用Thread类或者异步模式来创建我们自己的线程来实现异步编程。在.NET中有三种不同的异步模型:

1.APM模型

2.EAP模型

但是,遗憾的是,上面的这两种模型都不是微软所推荐的,所以我们将不对其做具体的讨论。如果你对这两种模型感兴趣,请移步:

https://msdn.microsoft.com/en-us/library/ms228963(v=vs.110).aspx

https://msdn.microsoft.com/en-us/library/ms228969(v=vs.110).aspx

3.TAP模型:这是微软所推荐的,我们稍后将会详细的进行讲解。

我们是否需要启用多线程

如果我们在.NET 4.5版本中使用异步编程模型,绝大部分情况下我们无需手动创建线程。因为编译器已经为我们做了足够多的工作了。

创建一个新线程不仅耗费资源而且花费时间,除非我们真的需要去控制一个多线程,否则是不需要去创建的。因为TAP模型和TPL模型已经为异步编程和并行编程提供了绝好的支持。TAP模型和TPL模型使用Task来进行操作,而Task类使用线程池中的线程来完成操作的。

Task可以在如下场景中运行:

  1. 当前线程
  2. 新线程
  3. 线程池中的线程
  4. 没有线程

在我们使用Task过程中,我们无需担心线程的创建或者是使用,因为.NET framework已经为我们处理好了各种细节。但是如果我们需要控制线程的话,比如说以下的场景:

  1. 为线程设置名称
  2. 为线程设置优先级
  3. 设置线程为后台线程

那么我们不得不使用Thread类来控制。

async和await关键字

.NET framework引入了两个新的关键字“async”和“await”来执行异步编程。当在方法上使用await关键字的时候,我们需要同时使用async关键字。await关键字是在调用异步方法之前被使用的。它主要是使方法的后续执行编程异步的,例如:

  1. private asyncstatic void CallerWithAsync()// async modifier is used
  2. {
  3. // await is used before a method call. It suspends
  4. //execution of CallerWithAsync() method and control returs to the calling thread that can
  5. //perform other task.
  6. string result = await GetSomethingAsync();
  7.  
  8. // this line would not be executed before GetSomethingAsync() //method completes
  9. Console.WriteLine(result);
  10. }

在这里,async关键字只能够被那些返回Task或者是void的方法所使用。它不能被用于Main入口方法上。

我们不能够在所有的方法上使用await关键字,因为一旦方法上有了await关键字,那么我们的返回类型就变成了“awaitable “类型。下面的集中类型是” awaitable “:

  1. Task
  2. Task<T>

TAP模型

首先,我们需要一个能够返回Task或者Task<T>的异步方法。我们可以通过如下方式来创建Task:

  1. Task.Factory.StartNew方法:在.net 4.5版本之前(.net 4)中,这是默认的创建和组织task的方式。
  2. Task.Run或者Task.Run<T>方法:从.net 4.5版本开始,这个方法被引进来了。这个方法足以能够应付大多数的场景。
  3. Task.FromResult方法:如果方法已经执行完毕并返回结果,我们可以使用这个方法来创建一个task。

Task.Factory.StartNew还有一些高级应用场景,请移步:http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx

下面的链接则展示了创建Task的几种方式: 
http://dotnetcodr.com/2014/01/01/5-ways-to-start-a-task-in-net-c/

Task的创建和等待

我们接下来将会利用Task.Run<T>方法来创建我们自己的Task。它会将某个特殊的方法放入位于ThreadPool的执行队列中,然后会为这些方法返回一个task句柄。下面的步骤将会为你展示我们如何为一个同步方法创建异步的Task的:

  1. 我们有一个同步的方法,需要耗费一些时间来执行完毕:
  1. static string Greeting(string name)
  2. {
  3. Thread.Sleep();
  4. return string.Format("Hello, {0}", name);
  5. }
  1. 为了让此方法一步执行,我们需要对其进行异步方法的包装。这个异步方法我们暂定为“GreetingAsync“。
  1. static Task<string> GreetingAsync(string name)
  2. {
  3. return Task.Run<string>(() =>
  4. {
  5. return Greeting(name);
  6. });
  7. }

现在我们可以使用await关键字来调用GreetingAsync方法

  1. private asyncstatic void CallWithAsync()
  2. {
  3. //some other tasks
  4. string result = awaitGreetingAsync("Bulbul");
  5. //We can add multiple ‘await’ in same ‘async’ method
  6. //string result1 = await GreetingAsync(“Ahmed”);
  7. //string result2 = await GreetingAsync(“Every Body”);
  8. Console.WriteLine(result);
  9. }

当“CallWithAsync”方法被调用的时候,它首先像正常的同步方法被调用,直到遇到await关键字。当它遇到await关键字的时候,它会暂定方法的执行,然后等待“GreetingAsync(“Bulbul”)”方法执行完毕。在此期间,这个控制流程会返回到“CallWithAsync”方法上,然后调用者就可以做其他的任务了。

当“GreetingAsync(“Bulbul”)”方法执行完毕以后,“CallWithAsync”方法就会唤醒在await关键字后面的其他的方法,所以在这里它会继续执行“Console.WriteLine(result)”方法。

  1. 任务的继续执行: “ContinueWith”方法则表明任务的持续执行。
  1. private static void CallWithContinuationTask()
  2. {
  3. Task<string> t1 = GreetingAsync("Bulbul");
  4. t1.ContinueWith(t =>
  5. {
  6. string result = t.Result;
  7. Console.WriteLine(result);
  8. });
  9. }

当我们使用ContinueWith方法的时候,我们无需使用await关键字。因为编译器会自动的将await关键字放到正确的位置上。

等待多个异步方法

让我们先看下面的代码:

  1. private asyncstatic void CallWithAsync()
  2. {
  3. string result = awaitGreetingAsync("Bulbul");
  4. string result1 = awaitGreetingAsync("Ahmed");
  5. Console.WriteLine(result);
  6. Console.WriteLine(result1);
  7. }

在这里,我们在顺序的等待两个方法被执行。GreetingAsync("Ahmed")方法将会在GreetingAsync("Bulbul")执行后,再执行。但是,如果result和result1彼此不是独立的,那么await关键字这样用是不合适的。

在上面的场景中,我们其实是无需使用await关键字的。所以方法可以被改成如下的样子:

  1. private async static void MultipleAsyncMethodsWithCombinators()
  2. {
  3.  
  4. Task<string> t1 = GreetingAsync("Bulbul");
  5. Task<string> t2 = GreetingAsync("Ahmed");
  6. await Task.WhenAll(t1, t2);
  7. Console.WriteLine("Finished both methods.\n " +
  8. "Result 1: {0}\n Result 2: {1}", t1.Result, t2.Result);
  9. }

在这里我们用了Task.WhenAll方法,它主要是等待所有的Task都完成工作后再触发。Task类还有另一个方法:WhenAny,它主要是等待任何一个Task完成就会触发。

异常处理

当进行错误处理的时候,我们不得不将await关键字放到try代码块中。

  1. private asyncstatic void CallWithAsync()
  2. {
  3. try
  4. {
  5. string result = awaitGreetingAsync("Bulbul");
  6. }
  7. catch (Exception ex)
  8. {
  9. Console.WriteLine(&ldquo;handled {}&rdquo;, ex.Message);
  10. }
  11. }

但是,如果我们有多个await关键字存在于try代码快中,那么只有第一个错误被处理,因为第二个await是无法被执行的。如果我们想要所有的方法都被执行,甚至当其中一个抛出异常的时候,我们不能使用await关键字来调用他们,我们需要使用Task.WhenAll方法来等待所有的task执行。

  1. private asyncstatic void CallWithAsync()
  2. {
  3. try
  4. {
  5.  
  6. Task<string> t1 = GreetingAsync("Bulbul");
  7.  
  8. Task<string> t2 = GreetingAsync("Ahmed");
  9.  
  10. await Task.WhenAll(t1, t2);
  11. }
  12. catch (Exception ex)
  13. {
  14. Console.WriteLine(&ldquo;handled {}&rdquo;, ex.Message);
  15. }
  16. }

尽管所有的任务都会完成,但是我们可以从第一个task那里看到错误。虽然它不是第一个抛出错误的,但是它是列表中的第一个任务。

如果想要从所有的任务中获取错误,那么有一个方式就是将其在try代码块外部进行声明。然后检查Task方法的“IsFaulted”属性。如果有错误抛出,那么其“IsFaulted”属性为true。

示例如下:

  1.  
  1. static async void ShowAggregatedException()
  2. {
  3. Task taskResult = null;
  4. try
  5. {
  6. Task<string> t1 = GreetingAsync("Bulbul");
  7. Task<string> t2 = GreetingAsync("Ahmed");
  8. await (taskResult = Task.WhenAll(t1, t2));
  9. }
  10. catch (Exception ex)
  11. {
  12. Console.WriteLine("handled {0}", ex.Message);
  13. foreach (var innerEx in taskResult.Exception.InnerExceptions)
  14. {
  15. Console.WriteLine("inner exception {0}", nnerEx.Message);
  16. }
  17. }
  18. }

Task的取消执行

如果直接使用ThreadPool中的Thread,我们是无法进行取消操作的。但是现在Task类提供了一种基于CancellationTokenSource类的方式来取消任务的执行,可以按照如下步骤来进行:

  1. 异步方法需要附带一个“CancellationToken”类型。
  2. 创建CancellationTokenSource类的实例:
  3. 将CancellationToken传递给异步方法:
  4. 对于长时间执行的方法,如果想取消的话,我们需要调用CancellationToken的ThrowIfCancellationRequested方法。
  5. 捕捉Task抛出的 OperationCanceledException。
  6. 现在,如果我们通过调用CancellationTokenSource的cancel方法来取消当前的操作的话,对于那些长时间运行的操作,将会抛出OperationCanceledException错误。我们也可以通过设置超时时间来取消任务。更多关于CancellationTokenSource类的信息,请移步:https://msdn.microsoft.com/en-us/library/system.threading.cancellationtokensource%28v=vs.110%29.aspx
  1. var cts = new CancellationTokenSource();
  2. Task<string> t1 = GreetingAsync("Bulbul", cts.Token);
  3. static string Greeting(string name, CancellationToken token)
  4. {
  5. Thread.Sleep();
  6. token.ThrowIfCancellationRequested();
  7. return string.Format("Hello, {0}", name);
  8. }

下面让我们看看如何设置超时时间来取消任务的执行:

  1. static void Main(string[] args)
  2. {
  3. CallWithAsync();
  4. Console.ReadKey();
  5. }
  6.  
  7. async static void CallWithAsync()
  8. {
  9. try
  10. {
  11. CancellationTokenSource source = new CancellationTokenSource();
  12. source.CancelAfter(TimeSpan.FromSeconds());
  13. var t1 = await GreetingAsync("Bulbul", source.Token);
  14. }
  15. catch (OperationCanceledException ex)
  16. {
  17. Console.WriteLine(ex.Message);
  18. }
  19. }
  20.  
  21. static Task<string> GreetingAsync(string name, CancellationToken token)
  22. {
  23. return Task.Run<string>(() =>
  24. {
  25. return Greeting(name, token);
  26. });
  27. }
  28.  
  29. static string Greeting(string name, CancellationToken token)
  30. {
  31. Thread.Sleep();
  32. token.ThrowIfCancellationRequested();
  33. return string.Format("Hello, {0}", name);
  34. }

并行编程

在.net 4.5中,存在一个叫做“Parallel”的类,这个类可以进行并行操作。当然这种并行和那些充分利用cpu计算能力的Thread 是有差别的,简单说起来,它有两种表现方式:

1.数据并行。 如果我们有很多的数据需要计算,我们需要他们并行的进行,那么我们可以使用For或者ForEach方法来进行:

  1. ParallelLoopResult result =
  2. Parallel.For(, , async (int i) =>
  3. {
  4. Console.WriteLine("{0}, task: {1}, thread: {2}", i,
  5. Task.CurrentId, Thread.CurrentThread.ManagedThreadId);
  6. await Task.Delay();
  7.  
  8. });

如果我们在计算的过程中,想要中断并行,我们可以把ParallelLoopState当做参数传递进去,我们就可以实现认为和中断这种循环:

  1. ParallelLoopResult result =
  2. Parallel.For(, , async (int i, ParallelLoopState pls) =>
  3. {
  4. Console.WriteLine("{0}, task: {1}, thread: {2}", i,
  5. Task.CurrentId, Thread.CurrentThread.ManagedThreadId);
  6. await Task.Delay();
  7. if (i > ) pls.Break();
  8. });

需要注意的是,当我们需要中断循环的时候,由于其运行在诸多个线程之上,如果线程数多于我们设定的中断数时,上述的执行可能就不太准确。

  1. 任务并行。如果我们想要多个任务并行处理,那么我们可以使用Parallel.Invoke方法来接受Action委托数组。例如:
  1. static void ParallelInvoke()
  2. {
  3. Parallel.Invoke(MethodOne, MethodTwo);
  4. }

参考文章:http://www.codeproject.com/Articles/996857/Asynchronous-programming-and-Threading-in-Csharp-N?bmkres=exist#_articleTop

.Net中的异步编程总结的更多相关文章

  1. C#中的异步编程Async 和 Await

    谈到C#中的异步编程,离不开Async和Await关键字 谈到异步编程,首先我们就要明白到底什么是异步编程. 平时我们的编程一般都是同步编程,所谓同步编程的意思,和我们平时说的同时做几件事情完全不同. ...

  2. .NET中的异步编程——常见的错误和最佳实践

    在这篇文章中,我们将通过使用异步编程的一些最常见的错误来给你们一些参考. 背景 在之前的文章<.NET中的异步编程——动机和单元测试>中,我们开始分析.NET世界中的异步编程.在那篇文章中 ...

  3. javaScript中的异步编程模式

    1.事件模型 let button = document.getElementById("my-btn"); button.onclick = function(event) { ...

  4. Netty 中的异步编程 Future 和 Promise

    Netty 中大量 I/O 操作都是异步执行,本篇博文来聊聊 Netty 中的异步编程. Java Future 提供的异步模型 JDK 5 引入了 Future 模式.Future 接口是 Java ...

  5. 一文说通C#中的异步编程

    天天写,不一定就明白. 又及,前两天看了一个关于同步方法中调用异步方法的文章,里面有些概念不太正确,所以整理了这个文章.   一.同步和异步. 先说同步. 同步概念大家都很熟悉.在异步概念出来之前,我 ...

  6. 一文说通C#中的异步编程补遗

    前文写了关于C#中的异步编程.后台有无数人在讨论,很多人把异步和多线程混了. 文章在这儿:一文说通C#中的异步编程 所以,本文从体系的角度,再写一下这个异步编程.   一.C#中的异步编程演变 1. ...

  7. promise 的基本概念 和如何解决js中的异步编程问题 对 promis 的 then all ctch 的分析 和 await async 的理解

    * promise承诺 * 解决js中异步编程的问题 * * 异步-同步 * 阻塞-无阻塞 * * 同步和异步的区别? 异步;同步 指的是被请求者 解析:被请求者(该事情的处理者)在处理完事情的时候的 ...

  8. .NET中的异步编程

    开篇 异步编程是程序设计的重点也是难点,还记得在刚开始接触.net的时候,看的是一本c#的Winform实例教程,上面大部分都是教我们如何使用Winform的控件以及操作数据库的实例,那时候做的基本都 ...

  9. 全面解析C#中的异步编程

    当我们处理一些长线的调用时,经常会导致界面停止响应或者IIS线程占用过多等问题,这个时候我们需要更多的是用异步编程来修正这些问题,但是通常都是说起来容易做起来难,诚然异步编程相对于同步编程来说,它是一 ...

随机推荐

  1. UnityShader之固定管线Fixed Function Shader【Shader资料3】

    Fixed function shader简介:  属于固定渲染管线 Shader, 基本用于高级Shader在老显卡无法显示时的情况.使用的是ShaderLab语言,语法与微软的FX files 或 ...

  2. Web应用程序系统的多用户权限控制设计及实现-总述【1】

    中大型的Web系统开发均需要权限的配置,基于多角色,多用户的操作权限管理是一个系统开发的基础.搭建好一套权限,用户,角色,页面一体的开发架构,可以用于后期业务的开发,同时也可用于不同业务的系统开发. ...

  3. Android 之 自动匹配字符AutoCompleteTextView

    AutoCompleteTextView是自动匹配字符,当我们输入一个单词或一段话的前几个字时,就会自动为你匹配后面的内容看效果图: 下面是代码: MainActivit: package com.e ...

  4. LeetCode 2 Add Two Numbers(链表操作)

    题目来源:https://leetcode.com/problems/add-two-numbers/ You are given two linked lists representing two ...

  5. 一个基于DDD的开源项目,各种技术!

    基于asp.net mvc + DDD 构架的开源.net cms系统. 运行截图: 特性: 跨平台 支持Windows.Linux.MacOX运行.linux运行案例:http://blog.ops ...

  6. JavaScript Patterns 3.5 JSON

    JSON: JavaScript Object Notation {"name": "value", "some": [1, 2, 3]}  ...

  7. C语言指针学习(续)

    五.数组和指针的关系 int array[10] = {0,1,2,3,4,5,6,7,8,9},value; ... ... value = array[0];//也可以写成 value = *ar ...

  8. 比较全面的MySQL优化参考(上下篇)

    转自:http://imysql.com/2015/05/24/mysql-optimization-reference-1.shtml 本文整理了一些MySQL的通用优化方法,做个简单的总结分享,旨 ...

  9. oracle定时任务

    一.简介 当我们需要oracle数据库定时自动执行一些脚本,或进行数据库备份.数据库的性能优化,包括重建索引等工作是需要使用到定时任务. 定时任务可以使用以下两种完成. 1.操作系统级的定时任务,wi ...

  10. Heartbeat+LVS构建高可用负载均衡集群

    1.heartbeat简介: Heartbeat 项目是 Linux-HA 工程的一个组成部分,它实现了一个高可用集群系统.心跳服务和集群通信是高可用集群的两个关键组件,在 Heartbeat 项目里 ...