下属内容主要分为9个部分。
异步编程的概念,如何取消异步编程,异步任务如何超时,异步线程通讯,再同步方法中调用异步方法,ValueTask,在异步应用和取消长时间执行的同步任务,同时完成任务
1.异步编程的概念
1.异步编程要求不阻塞状态
2.线程适合长期运行,计算量大,线程创建销毁会较大影响效率
3.异步,适合密集操作,适合时间少的小任务,可以避免线程阻塞
1.1异步任务Task
1.Task是一个引用类型,包含正在运行,完成,结果,报错等任务状态
2.异步会借助线程池在其他线程上运行
3.获取结果后回到之前的状态,
4.Task包含有返回值,其中有,任务状态和结果
5.将方法标记asyn后,可以在方法中await
6.异步任务的不阻塞状态,是当进入线程时会自动开辟一个线程去执行
7.async void,在UI内容需要异步执行时,会使用async对事件进行形容。并且由于async void没有task方法,所以异常报错将不能在外部被捕获,所以建议仅在无返回值的事件中使用
8.异步方法中不能使用阻塞方法,会导致底层同步运行,例如(Thread.sleep(10)可以更换为await Task.Delay(20),进行暂停异步中的线程执行)。补充:在一个方法中有多个Task存在时,他会在方法对每个Task循序执行,如果被await形容时,则会等待对应方法执行结束。所以在async形容的方法中建议使用(await Task.Delay(20)作为线程等待操作)
1.2 Task的底层逻辑
1.async和await会将方法包装成一个状态机,await相当于检查点,通过对状态机进行切换状态实现
2.如何取消异步编程
2.1使用CancellationTokenSource
static async Task Main(string[] args)
{
//设置cts的超时
var cts = new CancellationTokenSource(3000);
//获取cts的标识符
var token=cts.Token;
try
{
//模拟正在执行的任务
await Task.WhenAll(Task.Delay(50000, token));
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
//将cts进行dispose
cts.Dispose();
}
Console.ReadKey();
}
2.2在大部分带有异步编程的.net库中,都支持传入Token
static async Task Main(string[] args)
{
//设置cts的超时
var cts = new CancellationTokenSource(3000);
//获取cts的标识符
var token=cts.Token;
try
{
//模拟正在执行的任务
await Task.WhenAll(Task.Delay(50000, token));
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
//将cts进行dispose
cts.Dispose();
}
Console.ReadKey();
}
2.3可以使用带有Token的重载方法进行
static async Task Main(string[] args)
{
//设置cts的超时
var cts = new CancellationTokenSource(500);
//获取cts的标识符
var token=cts.Token;
//注册token的善后工作
token.Register(() => { });
Program program = new Program();
try
{
Console.WriteLine("run");
string a= await program.StrAsync(token);
Console.WriteLine(a);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
//将cts进行dispose
cts.Dispose();
}
Console.ReadKey();
}
async Task AsAsync(int delay, CancellationToken? cancellationToken = null)
{
var token=cancellationToken??CancellationToken.None;
//模拟方法中存在的一些变量
string a = "1",b="b";
//声明异常取消委托,在函数被跳出时,需要对函数中的一些参数进行内存清空
token.Register(() => { a = "";b = ""; });
//模拟同步方法中耗时的操作。
//但是由于同步方法不能被操作,所以只能根据节点来校验是否处于cancel状态
Thread.Sleep(100);
//当校验到token被取消时,则抛出异常,可以被外部try catch捕获
if(token.IsCancellationRequested)
token.ThrowIfCancellationRequested();
Thread.Sleep(100);
}
在绝大部分情况下,.net的库都带有异步编程的方法,名称后面通常以async结尾。
在任何cts结束后,需要进行dispose,cts属于引用类型,并且始终在内存中存在,所以非常容易造成内存泄漏。
通过委托事件进行善后工作,其中Token的善后工作可以多次被注册,但是注册顺序链表类型,先进后出,永远最先执行最后注册的委托事件
3. 异步任务如何超时
3.1使用扩展超时函数的形式
static async Task Main(string[] args)
{
Program program = new Program();
var cts = new CancellationTokenSource(10000);
//获取cts的标识符
var token=cts.Token;
try
{
//设置超时时间
int TimeOut = 500;
var timeout = Task.Run(async () => { await program.tastAsync(token); }).Wait(TimeOut);
if (!timeout)
{
cts.Cancel();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
cts.Dispose();
}
Console.WriteLine("1");
Console.ReadKey();
}
async Task tastAsync(CancellationToken? cancellationToken=null)
{
try
{
var token = cancellationToken ?? CancellationToken.None;
await Task.Delay(3000, token);
Console.WriteLine("2");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
4. 异步线程通讯队列
static async Task Main(string[] args)
{
//创建线程通讯队列
var channel = Channel.CreateUnbounded<string>();
Program program = new Program();
var sender = program.send(channel.Reader);
var Writ = program.Wr(channel.Writer);
await Writ;
//触发生产完成
channel.Writer.Complete();
await sender;
Console.WriteLine("go");
Console.ReadKey();
}
/// <summary>
/// 出队
/// </summary>
/// <param name="reader"></param>
/// <returns></returns>
async Task send(ChannelReader<string> reader)
{
//C#8.0以上版本可以使用
//await foreach (var str in reader.ReadAllAsync())
//{
// await Task.Delay(200);
// Console.WriteLine(str);
//}
try
{
//判断是否生产完成,并且读取完成
while (!reader.Completion.IsCompleted)
{
string a = await reader.ReadAsync();
Console.WriteLine(a);
await Task.Delay(200);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
/// <summary>
/// 入队
/// </summary>
/// <param name="writer"></param>
/// <returns></returns>
async Task Wr(ChannelWriter<string> writer)
{
for (int i = 0; i < 20; i++)
{
//写入数据
await writer.WriteAsync(i.ToString());
Console.WriteLine("fall" + i);
await Task.Delay(100);
}
}
5. ValueTask
绝大部分情况下都不推荐使用。只有在对内存资源要求非常严格时再去使用
由于valueTask是一个值类型,所以在部分内存开销上会有更多的优势
通常是带有返回值的,大量重复运行的内容,但是有必须使用异步来完成的函数,才推荐使用ValueTask
由于ValueTask的善后工作过去的繁琐,所以正常情况下不推荐使用
static async Task Main(string[] args)
{
var res = await GetIntAsync(10);
Console.WriteLine(res);
Console.ReadKey();
}
public static async ValueTask<int> GetIntAsync(int id)
{
await Task.Delay(1000);
return id*id;
}
6. 在同步方法中调用异步方法
6.1使用阻塞的形式调用异步方法
static void Main(string[] args)
{
Foo().GetAwaiter().GetResult();
}
static async Task Foo()
{
await Task.Delay(1000);
}
6.2通过使用扩展方法
public class demo
{
//用于记录函数是否被执行结束
public bool ISLoadImage { get; set; }=false;
public demo()
{
//e=>throw e弹出错误信息
LoadImage().SafeFireAndForget(() => { ISLoadImage = true; },e=>throw e);
}
async Task LoadImage()
{
await Task.Delay(1000);
}
}
public static class TaskExtenSions
{
public static async void SafeFireAndForget(this Task task, Action? onCompleted = null, Action<Exception>? onError = null)
{
try
{
await task;
onCompleted?.Invoke();
}
catch (Exception ex)
{
onError.Invoke(ex);
}
}
}
7. 异步任务实现同步机制和信号量
使用异步信号门,实际效果类似于(Lock)
//设置信号量的门的宽度为1
public static SemaphoreSlim semaphore = new SemaphoreSlim(1);
static async Task Main(string[] args)
{
TimeSpan start = new TimeSpan(DateTime.Now.Ticks); //获取当前时间的刻度数
var tasks=new List<Task>();
//创建10个异步任务
for (int i = 0; i < 10; i++)
{
tasks.Add(comAsync());
}
await Task.WhenAll(tasks);
TimeSpan end = new TimeSpan(DateTime.Now.Ticks); //获取当前时间的刻度数
TimeSpan abs = end.Subtract(start).Duration(); //时间差的绝对值
Console.WriteLine(abs.TotalMilliseconds);//输出double类型值ms为单位
Console.ReadKey();
}
public static async Task comAsync()
{
//开启门
await semaphore.WaitAsync();
await Task.Delay(200);
//结束门
semaphore.Release();
}
8.在异步应用和取消长时间运行的同步任务
namespace 在异步应用和取消长时间运行的同步任务
{
internal class Program
{
static async Task Main(string[] args)
{
try
{
//创建超时
var cts = new CancellationTokenSource(3000);
//进入同步任务
var task = new CancelableThreadTask(LoadImage,ErrorTaskDispose, TaskDispose);
//等待同步任务并超时
await task.RunAsync(cts.Token);
cts.Dispose();
Console.ReadKey();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Console.ReadKey();
}
/// <summary>
/// 长时间的同步任务
/// </summary>
public static void LoadImage()
{
Thread.Sleep(5000);
Console.WriteLine("go");
}
/// <summary>
/// 定义同步任务的结束委托,通常用在再同步任务执行结束后,执行一些内容,或者清空内存
/// </summary>
public static void TaskDispose()
{
Console.WriteLine("Dispose");
}
/// <summary>
/// 定义捕获信息,可以讲异常进行进行捕获,并执行后续操作,通常进行清空内存
/// </summary>
/// <param name="e"></param>
public static void ErrorTaskDispose(Exception e )
{
Console.WriteLine("ErrorTaskDispose");
Console.WriteLine(e.ToString() );
}
}
public class CancelableThreadTask
{
private Thread? _thread;
private bool ok;
private readonly Action _action;
private readonly Action<Exception> _onError;
private readonly Action _onnCompleted;
private int _isRun = 0;
public CancelableThreadTask(Action action, Action<Exception> onError=null,Action? onnCompleted=null)
{
_action = action;
_onError = onError;
_onnCompleted = onnCompleted;
}
public Task RunAsync(CancellationToken token)
{
//原子操作,防止在多线程访问是出现问题
if (Interlocked.CompareExchange(ref _isRun,1,0)==1)
{
throw new InvalidOperationException(" Task is alread running");
}
var _tcs = new TaskCompletionSource<bool>();
_thread = new Thread(() =>
{
try
{
_action();
_tcs.TrySetResult(ok);
_onnCompleted();
}
catch (Exception e)
{
if (e is ThreadInterruptedException)
_tcs.TrySetCanceled();
else
_tcs.SetException(e);
//传出异常捕获信息
_onError?.Invoke(e);
}
finally { Interlocked.Exchange(ref _isRun, 0); }
});
token.Register(() =>
{
//超时时中断线程
if (Interlocked.CompareExchange(ref _isRun, 0, 1) == 1)
_thread.Interrupt();
});
_thread.Start();
return _tcs.Task;
}
}
}
9.多个异步任务同时完成
使用场景,当需要多个任务随意进行,但是当全部任务执行结束后,将全部任务同时进行下去。例如当我们需要在不同类和函数中去获取api或者访问文件时,并且需要当全部完成时返回一个状态。则就适合使用这种方式
namespace 多个异步任务同时完成
{
/// <summary>
/// 使用场景,当需要多个任务随意进行,但是当全部任务执行结束后,将全部任务同时进行下去
/// </summary>
internal class Program
{
//需要安装包Microsoft.VisualStudio.Threading
public static AsyncBarrier AsyncBarrier = new AsyncBarrier(3);
static async Task Main(string[] args)
{
Run1();
await Task.Delay(500);
Run2();
await Task.Delay(5000);
Run3();
Console.WriteLine("finally");
Console.ReadKey();
}
public static async Task Run1()
{
Console.WriteLine("run1");
await Task.Delay(1000);
await AsyncBarrier.SignalAndWait();
Console.WriteLine("1");
}
public static async Task Run2()
{
Console.WriteLine("run2");
await Task.Delay(1000);
await AsyncBarrier.SignalAndWait();
Console.WriteLine("2");
}
public static async Task Run3()
{
Console.WriteLine("run3");
await Task.Delay(1000);
await AsyncBarrier.SignalAndWait();
Console.WriteLine("3");
}
}
}