.NET并发编程-异步编程模型上
时间扑面而来,我们终将释怀
健康的活着,平静的过着
开心的笑着,适当的忙着
本系列学习在.NET中的并发并行编程模式,实战技巧
本小节开始学习任务编程模型。本系列保证最少代码呈现量,虽然talk is cheap, show me the code被奉为圭臬,我的学习习惯是,只学习知识点,代码不在当下立马要用的时候不会认真去读的,更何况在大多时候在手机阅读更不顺畅。
本小节介绍任务异步模型。作为开发人员,异步编程是对你技能集的一个重要补充。
1、异步编程模型APM
异步(asynchronous)操作是指请求开始不需要等待请求的返回,请求完成后再切换回来,期间可以做其他事情。就是不会闲着等待任何一个操作的完成。
并行主要和应用程序性能有关,它还可以利用现代多核计算机结构,增强对多线程CPU密集型工作,异步是并发的超集,主要面向I/O密集型操作,而不是CPU密集型操作。异步解决了延迟问题(任何需要很长时间才能运行的问题)。
2、.NET中的异步支持
2.1 Begin/End模式
原始的异步编程模式是将一个长时间运行的函数分为两个部分,一部分负责启动异步操作Begin,另一部分负责完成时回调End,称之为Begin/End模型。以读取文件为例的同步和异步编程如下所示
void ReadFileBlocking(string filePath, Action<byte[]> process)
{
using (var fileStream = new FileStream(filePath, FileMode.Open,
FileAccess.Read, FileShare.Read))
{
byte[] buffer = new byte[fileStream.Length];
int bytesRead = fileStream.Read(buffer, 0, buffer.Length);
process(buffer);
}
}
//以下为异步读取
IAsyncResult ReadFileNoBlocking(string filePath, Action<byte[]> process)
{
using (var fileStream = new FileStream(filePath, FileMode.Open,
FileAccess.Read, FileShare.Read, 0x1000,
FileOptions.Asynchronous))
{
byte[] buffer = new byte[fileStream.Length];
var state = Tuple.Create(buffer, fileStream, process);
return fileStream.BeginRead(buffer, 0, buffer.Length,
EndReadCallback, state);
}
}
void EndReadCallback(IAsyncResult ar)
{
var state = ar.AsyncState as Tuple<byte[], FileStream, Action<byte[]>>;
using (state.Item2) state.Item2.EndRead(ar);
state.Item3(state.Item1);
}
上面EndReadCallback就是读取完成后的回调函数,什么是回调,回调是一个用于加快程序速度的函数。异步编程使用回调创建新的线程来独立地运行方法。在异步运行时,程序通过一个用于注册另一个函数的延续的可重入函数来通知调用函数任何更新,包括失败、取消、进度和完成。这个过程需要一些时间才能产生结果。
Begin/End模式,将开始和回调通知分离开,代码看起来不像顺序结构那么顺眼。并且像上例state变量一样,我们需要维护一个状态以保证回调函数能访问到。
2.2 基于事件的异步编程
EAP(Event-based Asynchronous Programming)基于事件的异步编程模式是从.NET2开始引入。用事件代替以往的回调,但仍然不能剥离方法调用和事件处理程序。
3、基于任务异步编程模型
TAP模型淘汰了APM和EAP,如果你是C#使用者,建议使用TAP模型。TAP为异步代码提供了一种干净的声明式风格。
C#5.0开始,对象Task和Task<T>在关键字async和await的支持下,已经成为异步操作模型的主要组件。TAP模型只通过纯粹关注语法方面就解决了回调问题,从而绕过了推理代码中表达的事件序列出现的困难。C#5.0中的异步函数解决了耗费运行时间的延迟问题。
TAP核心思想就是将异步操作包进一个Task里面。异步执行的话,使用async关键字通知编译器该方法异步运行不阻塞。上面读取文件例子改为TAP模型如下
async Task ReadFileNoBlockingAsync(string filePath, Func<byte[], Task> process)
{
using (var fileStream = new FileStream(filePath, FileMode.Open,
FileAccess.Read, FileShare.Read, 0x1000,
FileOptions.Asynchronous))
{
byte[] buffer = new byte[fileStream.Length];
int bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length);
await process(buffer);
}
}
当运行到await关键字时,它会挂起调用方法并将控制权交换给它的调用者,直到所等待的任务完成为止。
3.1 异步取消
以下是用于取消Task或异步操作相关的.NET类型:
CancellationTokenSource负责创建取消令牌,并向该令牌的所有拷贝发送取消请求
CancellationToken是用于监控当前令牌状态的结构。
TAP模型中的取消支持,创建任务时传递取消令牌,然后异步操作会检查令牌的状态,并在触发请求时取消计算。像下面所示:
CancellationTokenSource cts = new CancellationTokenSource(); // 取消令牌
async Task<string> DownloadStockHistory(string symbol,
CancellationToken token) //传递
{
string stockUrl = CreateFinanceUrl(symbol);
var request = await new HttpClient().GetAsync(stockUrl, token); // 传递给方法以得取消
return await request.Content.ReadAsStringAsync();
}
//cts.Cancel(); // 出发取消令牌
或则使用CancellationToken来注册回调
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
Task.Run(async ()=> {
var webClint = new WebClient();
token.Register(()=>webClint.CancelAsync());
var data = await webClint.DownloadDataTaskAsync(url);
},token);
在之前版本如果没有async和await时,通过手动检查取消,定期使用token.ThrowIfCancellationRequested方法检查取消
3.2 协作取消支持
使用CancellationTokenSource可以轻松创建由多个其他令牌组成的组合令牌。像下面所示有一个取消原因被触发就执行取消操作。
CancellationTokenSource ctsOne = new CancellationTokenSource()
CancellationTokenSource ctsTwo = new CancellationTokenSource();
CancellationTokenSource ctsComposite = CancellationTokenSource.CreateLinkedTokenSource(ctsOne.Token,ctsTwo.Token);
CancellationToken token = ctsComposite.Token;
字数有点超了哈,想要每个小片1k字左右的,剩下的异步重试,错误处理放在下小节吧。
时间扑面而来,我们终将释怀
健康的活着,平静的过着
开心的笑着,适当的忙着