闲来无事研究研究.Net中的异步编程
以前写异步操作的时候要不就是“直接定义一个Thread线程或者往线程池中扔一个委托方法”、“要不就是定一个委托变量然后通过委托的BeginInvoke和EndInvoke来实现异步操作以及异步操作结果的收集”。虽然.可跨平台的Net Core 3.0都出来了,.Net framework也干到4.7了,但是多年来写代码一直使用那两种比较传统的方式,今天闲来无事研究研究.Net 4上新加进来的任务Task,发现这东西确实不错,虽然本质上也是开一个线程出去,不过跟传统写法比起来好处确实大大的,主要总结如下几点:
使用Task的好处:
1、Task本身其实也是一个线程;
2、Task可以控制线程的先后执行顺序,通过Task.WaitAll(t1, t4);可以等待指定任务执行完再继续执行其它代码;
3、通过使用CancellationTokenSource的Cancel()结合Task方法内部的CancellationTokenSource.Token.ThrowIfCancellationRequested()可以取消某个正在运行的任务,
如果多个任务方法中都有CancellationTokenSource.Toke.ThrowIfCancellationRequested()方法,那么还可以用一个开关一下取消多个任务;
4、执行完的Task可以查看其执行状态,是取消、正常完成、还是异常出错(同样是动作执行完成,如果用传统代码自己往出死磕估计费很多代码量);
5、以上几点虽然通过传统的代码逻辑也能实现,但是没有使用Task直观,需要自己实现很多逻辑控制代码;
*使用async结合await实现异步方法的好处:
1、比较直观,可以将传统需要写在BeginInvoke和EndInvoke的代码写在同一个方法中,中间用await隔开就行,await放到比较耗时的方法前边;
2、async和await必须同时出现,await必须出现在有async修饰的方法中,其实await语句的下一句就是endinvoke中需要执行的代码;
3、调用async方法时,如果主调代码是在带有async修饰的方法中,那么在调用异步方法M2时前边加上await,直接用返回类型接住结果即可;
*4、如果调用async方法所在的代码方法外围没有async修饰符的方法中时,那么只能直接调用M2,而且需要用Task<返回类型>来接住结果,列如Task<int> w,这时候是异步调用, 如果想同步取结果,那么直接调用w.Result就可以达到阻塞的目的,当然阻塞之前是可以先执行一些其它代码的;
废话少说,直接上代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace TestTaskAndAsync
{
class Program
{
static void Main(string[] args)
{
//是否执行Task测试代码
bool isTestTask = false;
if (isTestTask) //Task测试代码
{
/*
* 使用Task的好处:
* 1、Task本身其实也是一个线程;
* 2、Task可以控制线程的先后执行顺序,通过Task.WaitAll(t1, t4);可以等待指定任务执行完再继续执行其它代码;
* 3、通过使用CancellationTokenSource的Cancel()结合Task方法内部的CancellationTokenSource.Toke.ThrowIfCancellationRequested()可以取消某个正在运行的任务,
* 如果多个任务方法中都有CancellationTokenSource.Token.ThrowIfCancellationRequested()方法,那么还可以一下取消多个任务;
* 4、执行完的Task可以查看其执行状态,是取消、正常完成、还是异常出错;
* 5、以上几点虽然通过传统的代码逻辑也能实现,但是没有使用Task直观,需要自己实现很多逻辑控制代码;
*
* **/
try
{
#region 定义取消标记,使用这种标记可以结合 token.ThrowIfCancellationRequested(); 同时取消多个执行的Task
CancellationTokenSource tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
#endregion
#region Task t1
Task t1 = new Task(() =>
{
Console.WriteLine("Task t1 开始执行!");
System.Threading.Thread.Sleep(2000);
});
t1.ContinueWith(task =>
{
Console.WriteLine(Environment.NewLine + "Task t1执行完成,状态为: IsCanceled={0}\tIsCompleted={1}\tIsFaulted={2}", task.IsCanceled, task.IsCompleted, task.IsFaulted);
Console.WriteLine();
tokenSource.Cancel(); //触发取消操作
Console.WriteLine("成功在t1任务中发送取消信号!");
});
t1.Start();
#endregion
#region Task t2
Task t2 = new Task(() =>
{
try
{
Console.WriteLine("Task t2 开始执行!");
for (int i = 0; i < 20; i++)
{
token.ThrowIfCancellationRequested();
System.Threading.Thread.Sleep(200);
Console.WriteLine($"t2打印:{i}");
}
}
catch (Exception ex)
{
throw ex;
}
}, token);
t2.ContinueWith(task =>
{
Console.WriteLine(Environment.NewLine + "Task t2执行完成,状态为: IsCanceled={0}\tIsCompleted={1}\tIsFaulted={2}", task.IsCanceled, task.IsCompleted, task.IsFaulted);
Console.WriteLine();
});
t2.Start();
#endregion
#region Task t3
Task t3 = new Task(() =>
{
Console.WriteLine("Task t3 开始执行!");
int a = 0;
int b = 100 / a; //如果不是Cancellation抛出的异常,那么IsFalut就是True
}, token);
t3.ContinueWith(task =>
{
Console.WriteLine(Environment.NewLine + "Task t3执行完成,状态为: IsCanceled={0}\tIsCompleted={1}\tIsFaulted={2}", task.IsCanceled, task.IsCompleted, task.IsFaulted);
});
t3.Start();
#endregion
#region Task t4
Task t4 = new Task(() =>
{
Console.WriteLine("Task t4 开始执行!");
for (int i = 0; i < 20; i++)
{
//token.ThrowIfCancellationRequested();
System.Threading.Thread.Sleep(200);
Console.WriteLine($"t4打印:{i}");
}
}, token);
t4.ContinueWith(task =>
{
Console.WriteLine(Environment.NewLine + "Task t4执行完成,状态为: IsCanceled={0}\tIsCompleted={1}\tIsFaulted={2}", task.IsCanceled, task.IsCompleted, task.IsFaulted);
Console.WriteLine();
});
t4.Start();
#endregion
Task.WaitAll(t1, t4);
Console.WriteLine(Environment.NewLine + "等待t1、t4完成后, 对Task的主调完成!");
Console.WriteLine();
}
catch (Exception ex)
{
//这里可以捕获到Task内部抛出的异常
Console.WriteLine($"异常:{ex.InnerException.Message}");
}
Console.ReadLine();
}
else
{
/*
* 使用async结合await实现异步方法的好处:
* 1、比较直观,可以将传统需要写在BeginInvoke和EndInvoke的代码写在同一个方法中,中间用await隔开就行,await放到比较耗时的方法前边;
* 2、async和await必须同时出现,await必须出现在有async修饰的方法中,其实await语句的下一句就是endinvoke中需要执行的代码;
* 3、调用async方法时,如果主调代码是在带有async修饰的方法中,那么在调用M2时前边加上await,直接用返回类型接住结果即可;
* 4、如果调用async方法所在的代码方法外围没有async修饰符,那么只能直接调用需要用Task<返回类型>来接住结果,列如Task<int> w,这时候是异步调用,如果
* 想同步取结果,那么直接调用w.Result就可以达到阻塞的目的,当然阻塞之前是可以先执行一些其它代码的;
* **/
#region
string reqm1 = Guid.NewGuid().ToString();
string reqm2 = Guid.NewGuid().ToString();
M1(reqm1);
Task<int> w = M2(5, 7, reqm2);
Console.WriteLine("main 主调完成!");
Console.WriteLine($"main中直调M2打印结果:{w.Result}");
Console.ReadLine();
#endregion
}
}
static async void M1(string request)
{
Console.WriteLine($"{request}:M1开始调用M2!");
int k = await M2(3,5,request);
Console.WriteLine($"{request}:M1调用M2完成,返回结果为:{k}");
}
/// <summary>
///
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
static async Task<int> M2(int a, int b,string request)
{
Console.WriteLine($"{request}:M2 开始执行,传入参数为:{a},{b}");
await Task.Delay(2000);
Console.WriteLine($"{request}:M2 开始计算结果,传入参数为:{a},{b}");
return a + b;
}
}
}
我的学习成果分享一下,欢迎大家指正!

浙公网安备 33010602011771号