关于C#多线程和异步的一些事。 2024-03-30 学习, C# 暂无评论 ## 多线程(Thread)和异步(Task)的区别 > 多线程是实现异步操作的方式,在需要进行异步操作的地方,会放开当前主线程(不阻塞),IO、网络需要阻塞的延迟操作会在新的线程中执行,执行结果再同步到当前主线程,实现异步。 --- `Thread`和`Task`都可以实现异步 #### Thread > 真正意义上的线程,如果需要做一些轮询操作,可以在线程中`while(true){轮询}` ##### Thread.Sleep(时间) 让当前**线程** 阻塞当前线程多少时间,单位毫秒, 可以理解成在异步操作里的 `await Task.Delay(时间)` #### Task ★ > 可以理解成轻量级的`Thread`, Task可以被await,`Task`可以定义任务的返回值, ``` C# var tsk1 = Task.Run(() => { Console.WriteLine("等待五秒后"); Thread.Sleep(5000); //Task.Delay(5000) Console.WriteLine($"任务取消{source.Token.IsCancellationRequested}"); }); ``` - 注意事项 `Task.Delay(5000)` 返回值是一个Task,如果不await的话,是不阻塞的,起不到阻塞的作用,如果是有返回值的Task,可以用Task.Result获取返回值,但是当前线程会阻塞。 所以如果换成了`Task.Delay(5000)` 是不会等待的直接会执行下面 - 几乎所有的Task都可以传递cts.Token,用来取消任务,但是,需要注意的是,下面是错误写法(下次别这样写,很捞批) ``` C# var tsk1 = Task.Run(() => { Console.WriteLine("等待五秒后"); Thread.Sleep(5000); //Task.Delay(5000) Console.WriteLine($"任务取消{source.Token.IsCancellationRequested}"); }, cts.Token); ``` 上面的`cts.Token`没有意义,不会对当前的Task生效,需要手动处理 `await Task.Delay(5000,cts.Token)` 如果超时,这行会抛出任务取消的异常,因为里面有封装过。 这种写法是可以取消任务的 ``` C# public async void ctsCancel(CancellationToken token) { try { //直接Run 可以运行,但是发生异常不会进入catch var tsk1 = Task.Run(async () => { await testAction(token); }); //这里会把异常抛出,进入catch await tsk1; } catch (Exception ee) { Console.WriteLine(ee.Message); } } //方法体 private Task testAction(CancellationToken cancellationToken) { return Task.Run(async () => { var ls = Enumerable.Range(1, 100).ToList(); foreach (var item in ls) { cancellationToken.ThrowIfCancellationRequested(); Console.WriteLine(item); await Task.Delay(500); } }); } ``` > token在外部已经进行超时处理了,五秒后会取消, `cancellationToken.ThrowIfCancellationRequested();`自己写的异步方法需要把token传过来,进行判断。 #### Task.ContinueWith() 类似于Js的Then 回调函数 > 这是Task对象的一个方法,可以传一个参数(Action),两个参数(Action,执行条件) 执行条件的是一个枚举,可以点进去看,根据上一步的状态(报错、执行完成),判断是否往下执行。 TaskContinuationOptions.OnlyOnRanToCompletion ``` C# public void ContinueWith() { var tsk = Task.Run(() => { Console.WriteLine("第一层"); Thread.Sleep(1000); Console.WriteLine("第一层等待一秒后"); var num = new Random().Next(1, 20); if (num % 2 == 0) { return num.ToString(); } else { throw new Exception("奇数异常"); } }).ContinueWith(r => { //如果上一个流程有异常,这里是不会执行的,异常也捕捉不到。 Console.WriteLine($"r.Exception==null : {r.Exception == null}"); Console.WriteLine("第二层"); var res = r.Result; return res; }, TaskContinuationOptions.OnlyOnRanToCompletion); try { //这里可以把异常抛出 var res = tsk.Result; Console.WriteLine("任务成功" + res); } catch (Exception ee) { Console.WriteLine("任务取消" + ee.InnerException?.Message); } } ``` #### 小结 > Task.Result和await Task 可以理解成把异步执行的结果拿到当前线程来,如果不拿过来的话,结果、异常都是没办法知晓的,因为不在同一个线程。 所以,在使用Task时候,可以在内部处理可能遇到的异常,多个Task可以用ContinueWith给串起来,在串起来的过程中可以配置往下执行的条件。