C# - 实现异步编程的几种方式
最近看别人项目的时候需要改造有些年头写的关于异步编程的代码。旧代码的实现方式有点不一样,怕出差错,收集一下资料整理C# 实现异步编程的方式,强化一下印象。旧的方法已经不推荐用了,奈何有的人仍然会也这样的代码,或是历史代码遗留下来的,还是需要读懂的。
需要储备的知识:进程、线程(前台、后台、状态)、异步方法、异步的概念
一、几种方式总述
- 异步编程模型 (APM,Asynchronous Programming Model) 模式(也称 IAsyncResult 模式),在此模式中异步操作需要 BeginXXX 和 EndXXX 方法。该模式.NET 1.0就开始用,.NET中还有若干个长历史的老类保留这种实现异步的方法。新方案中不再推荐使用。
- 基于事件的异步模式 (EAP,Event-based Asynchronous Pattern),这种模式需要 Async 后缀,也需要一个或多个事件、事件处理程序委托类型和 EventArg 派生类型。 .NET 2.0 中引入的。 新方案中不再推荐使用。
- 基于任务的异步模式 (TAP, Task-based Asynchronous Pattern) 使用一种方法来表示异步操作的启动和完成。 TAP 是在 .NET 4.5 中引入的,基于.NET 4.0中新增的Task类型. 通过async 和 await 关键字使用编译器功能。它是 .NET Framework 中进行异步编程的推荐使用方法。
- TPL
1 。异步模式(APM)
其特点是:方法名字以 BeginXXX 开头,返回类型为 IAsyncResult,调用结束后需要 EndXXX。
说到底APM实质是基于委托来实现异步方法调用。定义委托引用异步方法,委托类型定义了Invoke方法用于调用同步方法,定义了BeginInvoke和EndInvoke方法用于使用异步模式。BeginInvoke方法的前几个参数是委托的参数,接着的是一个AsyncCallBack委托类型的参数,AsyncCallBack是一个委托(方法),IAsyncResult作为其唯一参数,当异步方法执行完毕后将调用这个AsyncCallBack委托引用的方法。IAsyncResult有个重要的属性 AsyncWaitHandle,他是一个 用来等待异步任务执行结束的一个同步信号。
摘录一段代码:
class AsyncAPM
{
public void async()
{
string i = "参数";
Console.WriteLine("调用异步方法前");
PostAsync(i);
Console.WriteLine("调用异步方法后");
}
delegate void AsyncFoo(string i);
private static void PostAsync(object o)
{
AsyncFoo caller = Myfunc;
caller.BeginInvoke(o.ToString(), FooCallBack, caller);
}
private static void FooCallBack(IAsyncResult ar)
{
ar.AsyncWaitHandle.WaitOne();
var caller = (AsyncFoo)ar.AsyncState;
caller.EndInvoke(ar);
}
private static void Myfunc(string i)
{
Console.WriteLine("通过委托来实现异步编程的");
}
}
.Net 中仍有如下的常用类支持 APM:Stream、SqlCommand、Socket、HttpWebRequest 等,这些类把使用委托的都封装成了Begingxxx/Endxxx了。
可参考:
https://www.cnblogs.com/oracleblogs/p/3275193.html
https://www.cnblogs.com/xietianjiao/p/7099094.html
2。 基于事件的异步模式(EAP)
EAP 的类的特点是:定义了一个带有Async后缀的方法,并一个异步方法配一个***Completed 事件。
public class AsyncEAP
{
public void ReadAsync(byte [] buffer, int offset, int count);
public event ReadCompletedEventHandler ReadCompleted;
}
使用很复杂,在自定义的类中实现该模式一般使用BackgroundWorker来实现异步调用同步方法。
使用更少,了解存在即可。.NET4.0后不推荐使用。
3 。基于任务的异步模式 (TAP)
3.1 Task类
Task是在ThreadPool的基础上推出的。Task 对象是一个的中心思想 基于任务的异步模式 首次引入.NET Framework 4 中。 因为由执行工作 Task 对象通常以异步方式执行在线程池线程上而不是以同步方式在主应用程序线程。
https://www.cnblogs.com/xiaojidanbai/p/13224172.html
任务Task和线程Thread的区别:
1、任务是架构在线程之上的,也就是说任务最终还是要抛给线程去执行。
2、任务跟线程不是一对一的关系,比如开10个任务并不是说会开10个线程,这一点任务有点类似线程池,但是任务相比线程池有很小的开销和精确的控制。
Task和Thread一样,位于System.Threading命名空间下!
Task 分为两种:
- Task,表示可以执行一个异步操作,声明如下:
public class Task : IAsyncResult, IDisposable { }
- Task,表示可以执行带有返回值的异步操作,声明如下:
public class Task<TResult> : Task { }
任务的创建就有很多种方法:
1 通过构造函数创建
//对于创建和计划必须分开的方案,用构造函数比较合适,以在稍后某个时间执行
var task1 = new Task(() => { });
var task2 = new Task<int>(()=>
{
int i = 0;
return i;
});
task.Start();
2 使用任务工厂:
var task1 = Task.Factory.StartNew(() => { });
var task2 = Task.Factory.StartNew(() =>
{
int i = 0;
return i;
});
3 通过Task.Run创建:
var task1 = Task.Run(() => { });
var task2 = Task.Run(() =>
{
int i = 0;
return i;
});
等。。。。。。。。。。。。。。
+分割线***********************+
3.2 TAP
TAP一般定义一个带有Async后缀的方法,并返回一个Task类型。
使用async和await关键字。
async: 当一个方法由async关键字标识,表明这个方法是异步方法,当它被调用时,会创建一个线程来执行。
async 只能修饰用于返回void,Task,Task<>的方法。
async在VS11后不能作为程序的入口点,即Main方法不能使用async修饰符。await修饰符只能用于返回Task的方法。
async和await这两个关键字由编译器转换为状态机,通过System.Threading.Tasks中的类实现异步编程。即async和await关键字只是编译器的功能。编译器最终会用Task类创建代码。
很多文档包括msdn都刻意提到async/await关键字不会创建新的线程,用async关键字写的函数中的代码都在调用线程中执行。这里是最容易混淆的地方,严格意义上这个说法不准确,异步编程必然是多线程的。**msdn文档里提到的不会创建新线程是指async函数本身不会直接在新线程中运行。**通过反编译分析,我们知道本质上是await调用的异步函数执行完成后回调状态机的MoveNext来执行余下未执行完成的代码,await调用的异步函数必然在某个地方——也许是嵌套了很深的一个地方——启动了一个新的工作线程?来完成导致我们要使用异步调用的耗时比较长的工作,比如网络内容读取。
异步方法的返回类型必须为 void、Task、Task 中的其中一种。
- void,表示无返回值,不关心异步方法执行后的结果,一般用于仅仅执行某一项任务,但是不关心结果的场景。
- Task,表示异步方法将返回一个 Task 对象,该对象通常用于判断异步任务是否已经完成,可以使用 taskObj.Wait() 方法等待,或者 taskObj.IsCompleted 判断。
- Task,表示异步方法将返回一个 Task 对象,该对象的 Result 属性则是异步方法的执行结果,调用该属性时将阻塞当前线程(异步方法未执行完成时)。
摘一段代码:
namespace Test1
{
class AsyncTAP
{
public void Demo()
{
Console.WriteLine("主线程,线程ID:" + Thread.CurrentThread.ManagedThreadId);
var result = FuncAsync();
Console.WriteLine(result.Result);
}
private static async Task<string> FuncAsync()
{
return await Task.Run(() =>
{
Console.WriteLine("await/async的线程ID:"+Thread.CurrentThread.ManagedThreadId);
return "这是返回值";
});
}
}
}
WinForm 中的事件处理方法都可以标记为 async、MVC 中的 Action 方法也可以标 记为 async、控制台的 Main 方法不能标记为 async。