您的位置:首页 > 健康 > 美食 > C#异步方法的简介和常用所遇到的异步任务的解决办法

C#异步方法的简介和常用所遇到的异步任务的解决办法

2024/10/6 14:34:25 来源:https://blog.csdn.net/m0_51559565/article/details/141604825  浏览:    关键词:C#异步方法的简介和常用所遇到的异步任务的解决办法

下属内容主要分为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进行disposects.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进行disposects.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进行disposects.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.Threadingpublic 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");}}
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com