摘自:http://www.codeproject.com/Articles/996857/Asynchronous-programming-and-Threading-in-Csharp-N(葡萄城控件技术团队翻译)

异步编程和线程处理是并发或并行编程非常重要的功能特征。为了实现异步编程,可使用线程也可以不用。将异步与线程同时讲,将有助于我们更好的理解它们的特征。

本文中涉及关键知识点

1. 异步编程

2. 线程的使用

3. 基于任务的异步模式

4. 并行编程

5. 总结

  • 异步编程

什么是异步操作?异步操作是指某些操作能够独立运行,不依赖主流程或主其他处理流程。通常情况下,C#程序从Main方法开始,当Main方法返回时结束。所有的操作都是按顺序执行的。执行操作是有序列的,一个操作必须等到其前面的操作完成才能够执行。如以下代码示例:

     static void Main(string[] args)     {        DoTaskOne();        DoTaskTwo();     }

“DoTaskOne”方法结束后,DoTaskTwo()才能够执行。

异步编程中常用后台运行的方法体现,主调用线程不会被阻塞。调用后台运行的方法后,执行流程会立即返回到调用的线程并继续执行其他任务。后台运行方法通常是用线程或任务来实现。

在上面的例子中,在“DoTaskOne”方法调用成功后,如果“DoTaskOne”是异步调用,执行流程立即返回到Main方法中,并继续执行“DoTaskTwo” 方法。

C#提供了Thread类创建线程实现异步编程,或者使用.NET提供的异步模式实现异步编程。.NET中提供了三种不同的异步模式:

1. 异步编程模型(APM)模式Asynchrocous Programming Model,比如:BeginXXX、IAsyncResult

2. 基于事件的异步模式(EAP)Event-Based Asynchronous Pattern,比如:DownloadContentAsync

3. 基于任务的异步模式(TAP)Task Parallel Library,比如:Task.Factory.StartNew

前两种模型微软官方并不推荐使用,本文不再详细描述。我们将详细讨论基于任务的异步模式(TAP):

线程的使用

在.NET 4.5中引入了异步编程模式,大部分情况下都不需要我们手动创建线程。编译器已经替代了开发人员来完成这项工作。

创建新线程是非常耗时的。一般情况下,异步和并行编程使用 “基于任务的异步模式(TAP)”和“任务并行库(TPL)”就够了。如果需要控制线程的功能则需要使用其他模式。

TAP和TPL都是基于任务。一般来说任务是从线程池中调用线程( 线程池.NET框架创建的和维护的线程集)。如果我们使用任务,就不需要直接调用线程池。

任务可以在以下情况运行:

1. 在正在运行的线程中

2. 在新线程中

3. 从线程池中的某一线程中

4. 没有线程也可以运行

如果使用任务机制,开发人员就不必担心线程的创建或使用,.NET框架已经为我们解决了这一难题。

有时候需要控制线程,执行以下操作:

1. 设置线程名称

2. 设置线程优先级

3. 设置线程是前端或后端运行

我们可以使用线程类来创建线程。

使用Thread类创建线程

Thread类的构造函数接收委托类型的参数

1. ThreadStart:定义了返回值为空的方法,且不带参数的方法。

2. ParameterizedThreadStart:定义了返回值为空且有一个object类型的参数。

面是一个简单的例子,使用 Start方法启动一个新线程:

static void Main(string[] args)
{
Thread thread = new Thread(DoTask);
thread.Start();// Start DoTask method in a new thread
//Do other tasks in main thread
}
static public void DoTask() {
//do something in a new thread
}

可以用Lamda表达式代替线程名称:

static void Main(string[] args)
{
Thread thread = new Thread(() => {
//do something in a new thread
}); thread.Start();// Start a new thread
//Do other tasks in main thread
}

如果不需要引用变量,可如下直接启动线程:

static void Main(string[] args)
{
new Thread(() => {
//do something in a new thread
}).Start();// Start a new thread //Do other tasks in main thread
}

但是,如果想控制线程对象,对线程设置一些属性,需要在线程创建后引用线程变量。如下可给线程对象的不同属性设值:

static void Main(string[] args)
{
Thread thread = new Thread(DoTask);
thread.Name = "My new thread";// Asigning name to the thread
thread.IsBackground = false;// Made the thread forground
thread.Priority = ThreadPriority.AboveNormal;// Setting thread priority
thread.Start();// Start DoTask method in a new thread
//Do other task in main thread
}

调用引用变量,可以执行一些操作如中止线程或通过调用join方法等待阻塞线程。

如果需要通过函数传值,可以给Start方法传值。由于该方法的参数为Object类型,因此需要强制转换类型。

static void Main(string[] args)
{
Thread thread = new Thread(DoTaskWithParm);
thread.Start("Passing string");// Start DoTaskWithParm method in a new thread
//Do other task in main thread
}
static public void DoTaskWithParm(object data)
{
//we need to cast the data to appropriate object
}

“async”和“await”关键字

.NET框架引入了两个新的关键字来实现异步编程:“async”和“await”。使用 “await”的异步方法必须由“async”修饰符来声明方法。“await”关键字修饰调用异步方法。await 运算符应用于一个异步方法中的任务以挂起该方法的执行,直到等待任务完成.如下:

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

而“ async ”修饰符只能用于返回值为Task类型或Void的方法。它不能用于主程序的切入点。

所有的方法之前不能使用await关键字,使用“await”关键字方法必须返回 “可等待”类型。以下属于“可等待”类型

1. Task

2. Task<T>

3. 自定义“可等待”类型。

  • 基于任务的异步模式

首先我们需要声明一个返回类型为Task或Task<T>的异步方法。可以通过以下几种方式创建任务:

1. Task.Factory.StartNew方法:在之前的.NET版本(在.NET 4中),是创建和启动任务的主要方法。

2. Task.Run或Task.Run <T>方法:从.NET 4.5这个方法已经被使用。此方法足以满足常见情况。

3. Task.FromResult方法:如果结果是已计算,就可以用这个方法来创建任务。

创建并等待一个任务

使用Task.Run <T>方法创建Task。该方法将特定工作按顺序排列在线程池中运行,并返回工作的任务句柄。需要以下步骤从同步方法中创建异步任务:

1. 假设下面方法是同步的,但需要一定的时间来完成:

static string Greeting(string name)
{
Thread.Sleep();
return string.Format("Hello, {0}", name);
}

2. 要以异步方式访问此方法,必须以异步方式封装。命名为“GreetingAsync”。增加“Async”的后缀命名异步方法。

            static Task<string> GreetingAsync(string name)
{
return Task.Run<string>(() =>
{
return Greeting(name);
});
}

3.现在,可通过使用的await关键字调用异步方法GreetingAsync

private async static void CallWithAsync()
{
//some other tasks
string result = await GreetingAsync("Bulbul");
//We can add multiple &ldquo;await&rdquo; in same &ldquo;async&rdquo; method
//string result1 = await GreetingAsync(&ldquo;Ahmed&rdquo;);
//string result2 = await GreetingAsync(&ldquo;Every Body&rdquo;);
Console.WriteLine(result);
}

当“CallWithAsync”方法被调用时,与常规的同步方法一样执行,直到遇到“await”的关键字。当它执行到 await的关键字会处理执行,并开始等待“GreetingAsync(” Bulbul “)” 方法被完成。同时,程序流将返回” CallWithAsync “方法的调用者,并继续执行调用者的任务。

当“GreetingAsync(" Bulbul ") 方法完成,“CallWithAsync”的方法恢复 “await关键字后的其他任务。在本实例中,将继续执行的代码“Console.WriteLine(result)”

4. 使用任务持续:Task类 “ContinueWith”的方法定义了Task完成后被调用的代码。

                           private static void CallWithContinuationTask()
{
Task<string> t1 = GreetingAsync("Bulbul");
t1.ContinueWith(t =>
{
string result = t.Result;
Console.WriteLine(result);
});
}

如果使用“ContinueWith”的方法就不需要使用“await“关键字,编译器会自动在合适的位置中添加“await”关键字。

等候多​​个异步方法。

看看下面的代码:

private async static void CallWithAsync()
{
string result = await GreetingAsync("Bulbul");
string result1 = await GreetingAsync(&ldquo;Ahmed&rdquo;);
Console.WriteLine(result);
Console.WriteLine(result1);
}

有两个正在等待调用函数序列。“GreetingAsync(” Ahmed “)” 会在完成第一个呼叫“GreetingAsync(” Bulbul “)” 之后启动。如果“result”和上面的代码“result1”是独立的,那么连续的“awiating”并不是一个好的做法。

在这种情况下,我们可以简化调用方法,不需要添加多个“await”关键字,只在一个地方添加await关键字,如下所示,这种情况下,该方法的调用都可以并行执行。

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

在这里,我们使用Task.WhenAll连接器。Task.WhenAll创建一个任务,将完成所有的提供的任务。Task类也有其他的结合器。Task.WhenAny,当所任务链中所有的任务完成时,结束使用。

处理异常

必须把“await的代码块放在try块内捕获异常。

                  private async static void CallWithAsync()
{
try
{
string result = await GreetingAsync("Bulbul");
}
catch (Exception ex)
{
Console.WriteLine(&ldquo;handled {}&rdquo;, ex.Message);
}
}

如果try块中有多个“await”,只有第一个” await“异常会被处理,其他“await”将无法被捕捉。如果希望所有的方法都能捕获异常,不能使用“await”关键字调用方法,使用Task.WhenAll来执行任务。

private async static void CallWithAsync()
{
try
{
Task<string> t1 = GreetingAsync("Bulbul");
Task<string> t2 = GreetingAsync("Ahmed");
await Task.WhenAll(t1, t2);
}
catch (Exception ex)
{
Console.WriteLine(&ldquo;handled {}&rdquo;, ex.Message);
}
}

捕获所有任务的错误一种方法是在try块之外声明任务,这样可以从try块进行访问,并检查任务的“IsFaulted”属性。如果它存在异常那么“IsFaulted”属性值为True,就可捕获任务实例的内部异常。

还有另一个更好的办法:

     static async void ShowAggregatedException()
{
Task taskResult = null;
try
{
Task<string> t1 = GreetingAsync("Bulbul");
Task<string> t2 = GreetingAsync("Ahmed");
await (taskResult = Task.WhenAll(t1, t2));
}
catch (Exception ex)
{
Console.WriteLine("handled {0}", ex.Message);
foreach (var innerEx in taskResult.Exception.InnerExceptions)
{
Console.WriteLine("inner exception {0}", nnerEx.Message);
}
}
}

取消任务

在此之前,如果从线程池中调用线程,线程是不可能取消。现在,Task类提供了一个方法基于CancellationTokenSource类能够取消已启动的任务,取消任务步骤:

1. 异步方法应该除外 “ CancellationToken” 参数类型

2. 创建CancellationTokenSource类实例:

var cts = new CancellationTokenSource();

3. 传递CancellationToken,如:

 Task<string> t1 = GreetingAsync("Bulbul", cts.Token);

4. 长时间运行的方法中,必须调用CancellationToken ThrowIfCancellationRequested()方法

 static string Greeting(string name, CancellationToken token)
{
Thread.Sleep();
token. ThrowIfCancellationRequested();
return string.Format("Hello, {0}", name);
}

5. 从等待的Task中捕获 OperationCanceledException异常。
6. 如果通过调用CancellationTokenSource的实例的方法执行取消操作,将从长时间运行操作中抛出

OperationCanceledException异常。也可以设置取消的时间。以下是完整的代码,一秒后执行取消操作:

        static void Main(string[] args)
{
CallWithAsync();
Console.ReadKey();
} async static void CallWithAsync()
{
try
{
CancellationTokenSource source = new CancellationTokenSource();
source.CancelAfter(TimeSpan.FromSeconds());
var t1 = await GreetingAsync("Bulbul", source.Token);
}
catch (OperationCanceledException ex)
{
Console.WriteLine(ex.Message);
}
} static Task<string> GreetingAsync(string name, CancellationToken token)
{
return Task.Run<string>(() =>
{
return Greeting(name, token);
});
} static string Greeting(string name, CancellationToken token)
{
Thread.Sleep();
token.ThrowIfCancellationRequested();
return string.Format("Hello, {0}", name);
}
  • 并行编程

.NET 4.5及以上版本推出“Parallel类,是线程类的抽象。使用“Parallel”类,我们可以实现并行。并行与线程不同,它使用所有可用的CPU或内核的。以下两种类型的并行是可行:

  1. 数据并行:如果我们有数据的大集合,我们希望在每个数据的某些操作进行并行使用,那么就可以使用数据并行。Parallel类有静态For或ForEach来执行数据并行行,如
ParallelLoopResult result =
Parallel.For(, , async (int i) =>
{
Console.WriteLine("{0}, task: {1}, thread: {2}", i,
Task.CurrentId, Thread.CurrentThread.ManagedThreadId);
await Task.Delay();
});

For或ForEach方法可以在多线程中和且索引无序可以是无序的。

如果想停止并行For或ForEach方法,可通过ParallelLoopState作为参数,并根据需要打破循环的状态,跳出循环。

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

2. 任务并行:如果想要同时运行多个任务的,我们可以通过调用Parallel类的invoke方法使用任务并行Parallel.Invoke方法接收委托行为的数组。例如:

static void ParallelInvoke()
{
Parallel.Invoke(MethodOne, MethodTwo);
}

将异步方法转为同步执行

public async Task<List<ReturnOrderPrintVO>> QueryReturnOrderPrintInfo(List<string> commodityStockOutIds)
List<ReturnOrderPrintVO> returnOrderList = Task.Run(() =>
                  _stockOutBll.QueryReturnOrderPrintInfo(commodityStockOutIds)
              ).Result;
new TaskFactory().StartNew(Action)
Task.Factory.StartNew(Action)
new Task(Action);
Task.Run(Action)

C#:异步编程和线程的使用(.NET 4.5 ),异步方法改为同步执行的更多相关文章

  1. C#:异步编程和线程的使用(.NET 4.5 )

    摘自:http://www.codeproject.com/Articles/996857/Asynchronous-programming-and-Threading-in-Csharp-N(葡萄城 ...

  2. 异步编程和线程的使用(.NET 4.5 )

    C#:异步编程和线程的使用(.NET 4.5 )   异步编程和线程处理是并发或并行编程非常重要的功能特征.为了实现异步编程,可使用线程也可以不用.将异步与线程同时讲,将有助于我们更好的理解它们的特征 ...

  3. C#异步编程の----Threadpool( 线程池)

    简介: 一个托管线程的创建需要数千个CPU周期,并且当发生线程切换时也会带来明显的开销.考虑线程的重用,避免不断重复创建新的线程是提高系统效率的一种方式. 线程池是一种提供效率的方式,它创建好一些线程 ...

  4. C#异步编程

    什么是异步编程 什么是异步编程呢?举个简单的例子: using System.Net.Http; using System.Threading.Tasks; using static System.C ...

  5. C# - 多线程 之 异步编程

    异步编程 同步编程,请求响应模型,同步化.顺序化.事务化. 异步编程,事件驱动模型,以 Fire and Forget 方式实现. 异步编程模式  -§- 异步编程模型 (APM) 模式: IAsyn ...

  6. JavaScript异步编程的主要解决方案—对不起,我和你不在同一个频率上

    众所周知(这也忒夸张了吧?),Javascript通过事件驱动机制,在单线程模型下,以异步的形式来实现非阻塞的IO操作.这种模式使得JavaScript在处理事务时非常高效,但这带来了很多问题,比如异 ...

  7. 使用 Async 和 Await 的异步编程(C# 和 Visual Basic)[msdn.microsoft.com]

    看到Microsoft官方一篇关于异步编程的文章,感觉挺好,不敢独享,分享给大家. 原文地址:https://msdn.microsoft.com/zh-cn/library/hh191443.asp ...

  8. .NET面试题解析(07)-多线程编程与线程同步

      系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 关于线程的知识点其实是很多的,比如多线程编程.线程上下文.异步编程.线程同步构造.GUI的跨线程访问等等, ...

  9. [C#] 谈谈异步编程async await

    为什么需要异步,异步对可能起阻止作用的活动(例如,应用程序访问 Web 时)至关重要. 对 Web 资源的访问有时很慢或会延迟. 如果此类活动在同步过程中受阻,则整个应用程序必须等待. 在异步过程中, ...

随机推荐

  1. @Transactional注解事务不起作用

    @Transactional注解事务不起作用 问题:今天在项目中碰到一个事务问题,使用@Transactional注解事务,抛出异常不会滚. 解决一:https://blog.csdn.net/u01 ...

  2. (寒假集训)Roadblock(最短路)

    Roadblock 时间限制: 1 Sec  内存限制: 64 MB提交: 9  解决: 5[提交][状态][讨论版] 题目描述 Every morning, FJ wakes up and walk ...

  3. 【bzoj1123】【[POI2008]BLO】tarjan判割点

    (上不了p站我要死了,侵权度娘背锅) Description Byteotia城市有n个 towns m条双向roads. 每条 road 连接 两个不同的 towns ,没有重复的road. 所有t ...

  4. [LOJ6280]数列分块入门 4

    题目大意: 给你一个长度为$n(n\leq50000)$的序列$A$,支持进行以下两种操作:​ 1.将区间$[l,r]$中所有数加上$c$: 2.询问区间$[l,r]$在模$c+1$意义下的和.思路: ...

  5. Android Developer -- Bluetooth篇 开发实例之一 扫描设备

    第一步:声明Bluetooth Permissions <!-- 设置蓝牙访问权限 --> <uses-permission android:name="android.p ...

  6. NDK安装教程 not a valid ndk directory -- Eclipse

    第一步:下载 Eclipse IDE for Java Developers http://www.eclipse.org/downloads/ 第二步:下载CDT http://www.eclips ...

  7. Delphi TClientDataset查找定位功能

    if CDSUserFunc.Locate('mod_id;res_id', VarArrayOf([UserFunc.MOD_ID, UserFunc.RES_ID]), [loCaseInsens ...

  8. 【GitHub】README.md文件中 markdown语法 插入超链接

    语法: [Spring-data-jpa 查询 复杂查询陆续完善中](http://www.cnblogs.com/sxdcgaq8080/p/7894828.html) [文本](URL) 效果:

  9. apache 启动脚本加入系统服务列表

    第一步:cp /usr/local/apache2/bin/apachectl /etc/init.d/httpd第二步:vim /etc/init.d/httpd在第一行#!/bin/sh下增加两行 ...

  10. jstl的错误总结与解决方法

    哎,真他娘的无语了,jstl标签竟然还与tomcat的版本有关.一会报错:java.lang.AbstractMethodError: javax.servlet.jsp.PageContext.ge ...