1.异步编程基础
首先声明,我是一个很菜很菜的菜鸟程序员,最近在学习异步编程,在看《C#并发编程经典实例》这本书,打算在看的过程中,在博客中做个记录,顺便把比较好的地方摘抄出来和大家分享。包括自己写的一些小段测试程序,希望能在大家学习路上一些小小的帮助。
1.1暂停一段时间
问题
需要让程序等待一段时间,这在实现单元测试或者实现重试延迟非常有用。
解决方案
Task类有一个返回Task对象的静态函数Delay,这个Task对象会在指定的时间后完成。
单元测试示例
下面的例子用于单元测试,定义了一个异步完成的任务。在模拟一个异步操作时,至少需要测试“同步成功”,“异步成功”和”异步失败三种情况“下面的例子返回一个Task对象,用于异步成功测试。
static async Task<T> DelayResult<T>(T result,TimeSpan timeSpan)
{
await Task.Delay(timeSpan);
Console.WriteLine("async is complete");
return result;
}
测试代码
再来看一个示例代码:
定义一个异步方法,并同时用Task.Delay是实现一个异步等待。通过比较首先执行完成的Task返回对象,判断异步方法是否执行超时。
static void Main(string[] args)
{
Task task = GetMetFask();
Console.ReadKey();
}
private static async Task<string> GetMetFask()
{
var tk1r = Methond1();
var tk2r = Task.Delay(10000);//等待操作
string str=string.Empty;
var completedtask =await Task.WhenAny(tk1r, tk2r);//判断哪个任务首先完成
if (completedtask == tk1r)
{
str =("Methond1完成");
}
if (completedtask == tk2r)
{
str = ("Methond2完成");
}
Console.WriteLine(str);
return str;
}
private static async Task Methond1()
{
await Task.Run(()=>{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(10);
}
});
}
Task.Delay适用于对异步代码进行单元测试或者实现重试逻辑,要实现超时功能的话,最好使用CancellationToken。
1.2返回完成的任务
问题
如何实现一个具有异步签名的同步方法。如果从异步接口或基类继承代码,但希望用同步方法来实现。对异步代码进行单元测试,以及用简单的生成方法存根(Stub)或者模拟对象(mock)来产生异步接口,这两种情况都可以用这个技术。
生成方法存根:生成方法存根 (Stub) 是一项 IntelliSense 自动代码生成功能,它提供了一种简便的方法,使 Visual Studio 在您编写方法调用时创建新的方法声明。Visual Studio 从调用推导声明。
解决方法
可以使用Task.FromResult方法创建并返回一个新的Task对象,这个Task对象已经是完成的,并且又正确的返回值。
示例代码
public class MySynchronousImplemention : IMyAsyncInterface
{
//实现异步方法
public Task<int> GetV()
{
return Task.FromResult<int>(13);//只能提供结构正确的同步Task对象
}
}
/// <summary>
/// 异步接口
/// </summary>
public interface IMyAsyncInterface
{
Task<int> GetV();//异步方法
}
Task.FromResult只能提供结果正确的同步Task对象,如果返回的Task对象有一个其他的类型结果(例如以NotImplementedException结束的Task对象)就需要自行创建使用TaskCompletion Source的辅助方法。
/// <summary>
/// 创建TaskCompletionSource辅助方法
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
static Task<T> NotImplementedAsync<T>()
{
var tcs = new TaskCompletionSource<T>();
tcs.TrySetException(new NotImplementedException());
return tcs.Task;
}
1.3 报告进度
【问题】
异步操作执行过程中,需要展示操作进度。
解决方案
使用IProgress< T>和Progress< T>类型。编写的async方法需要有IProgress< T>参数,其中T是需要报告的进度类型。
示例代码如下
static async Task MyMethondAsync(IProgress<double> progress=null)
{
double percentComplete = 0;
//报告进度操作
while (!done)
{
percentComplete++;
if (percentComplete>99)
{
done = true;
}
if (progress!=null)
{
progress.Report(percentComplete);
}
}
}
static async Task CallMyMethondAsync()
{
var progress = new Progress<double>();
progress.ProgressChanged += (sender, args) =>
{
//进度改变后事件操作
Console.WriteLine("正在进行");
};
await MyMethondAsync(progress);
}
1.3 等待一组任务完成
【问题】执行几个任务,等待他们全部完成
解决方案
框架提供的Task.WhenAll方法可以实现这个功能。这个方法输入为若干个任务,当所有的任务完成返回一个完整的Task对象
2.6 任务完成时的处理
【问题】正在await一批任务,希望每个任务完成时对它进行一些处理。另外,希望在任务已完成就立即进行处理,无需等待其他任务。
代码实例说明
【注意:此代码并非解决问题的方法】
class Program
{
static async Task<string> DelayAndReturnAsync(int val)
{
await Task.Delay(TimeSpan.FromSeconds(val));
return val.ToString();
}
static async Task ProcessTaskAsync()
{
Task<string> taskA = DelayAndReturnAsync(3);
Task<string> taskB = DelayAndReturnAsync(5);
Task<string> taskC = DelayAndReturnAsync(1);
var tasks = new Task<string>[] { taskA, taskB, taskC };//此集合要设置异步返回数据类型【或var tasks = new [] { taskA, taskB, taskC };】
foreach (var item in tasks)
{
var result = await item;
Console.WriteLine(result);
}
}
static async Task Main(string[] args)
{
await ProcessTaskAsync();
}
}
虽然TaskC是首先完成的,但是这段代买仍按照列表的顺序对任务进行await,我们希望按任务完成的次序进行处理而,不用等待其他任务。
解决方案
引用更高级的async方法来await任务对结果进行并行处理。
class Program
{
static async Task<int> DelayAndReturnAsync(int val)
{
await Task.Delay(TimeSpan.FromSeconds(val));
return val;
}
static async Task AwaitAndProcessAsync(Task<int> task)
{
var result = await task;
Console.WriteLine(result);
}
static async Task ProcessTaskAsync()
{
Task<int> taskA = DelayAndReturnAsync(3);
Task<int> taskB = DelayAndReturnAsync(5);
Task<int> taskC = DelayAndReturnAsync(1);
var tasks = new [] { taskA, taskB, taskC };
var processingTask = (from t in tasks select AwaitAndProcessAsync(t)).ToArray();
await Task.WhenAll(processingTask);
}
static async Task Main(string[] args)
{
await ProcessTaskAsync();
}
}
【重构,提出处理过程】
static async Task ProcessTaskAsync()
{
Task<int> taskA = DelayAndReturnAsync(3);
Task<int> taskB = DelayAndReturnAsync(5);
Task<int> taskC = DelayAndReturnAsync(1);
var tasks = new [] { taskA, taskB, taskC };
var processingTask = tasks.Select(async t =>
{
var result =await t;
Console.WriteLine(result);
}).ToArray();
await Task.WhenAll(processingTask);
}
2.7 处理async Task方法的异常
可以使用简单的try和catch来捕获异常,如下
static async Task Main(string[] args)
{
await AsyncException();
}
static async Task ThrowExceptionAsync()
{
await Task.Delay(TimeSpan.FromSeconds(3));
throw new InvalidOperationException("Error Message");
}
static async Task AsyncException()
{
try
{
await ThrowExceptionAsync();
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex.Message);
}
}
注意:只有Task对象被await调用时,才会引发异常。
【本博客中理论内容和部分示例代码来自《C#并发编程经典实例》[美]Stephen Cleary(著) 相银初(译)】