计算机技术 异步编程的概念及核心理念 Rainbow Bubbles 2025-07-27 2025-07-27 常见概念
已经有多线程了,为什么还要异步? 多线程与异步是不同的概念,异步并不意味着多线程,单线程同时可以异步。 异步默认借助线程池,多线程经常阻塞,而异步要求不阻塞。 多线程和异步的使用场景不同:
多线程:CPU密集型,长期运行的任务,线程的创建与销毁的开销都比较大 提供更多底层控制,操作线程,锁,信号量等,线程不易于传参及返回,线程 的代码书写较为繁琐
异步:适合IO密集型操作,适合短暂的小任务,避免线程阻塞,提高系统的响应能力
什么是异步任务(Task) 包含了异步任务的各种状态的一个引用类型,正在运行、完成、结果和报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var task = new Task<string >(() =>{ System.Threading.Thread.Sleep(1500 ); return "Task completed!" ; }); Console.WriteLine(task.Status.ToString()); task.Start(); Console.WriteLine(task.Status.ToString()); Thread.Sleep(1000 ); Console.WriteLine(task.Status.ToString()); Thread.Sleep(2000 ); Console.WriteLine(task.Status.ToString()); Console.WriteLine(task.Result);
1 2 3 4 5 6 7 PS D:\INFORMATION\dotnet\ConsoleApp> dotnet run Created WaitingToRun Running RanToCompletion Task completed! PS D:\INFORMATION\dotnet\ConsoleApp>
同时还是对于异步编程的抽象,开启异步任务后,当前线程并不会阻塞,而是可以去做其他的事情,异步任务(默认)会借助线程池在其他线程上运行,获取结果后回到之前的状态。 任务的结果: 返回值为Task的方法表示异步任务没有返回值 返回值为Task泛型类型则表示有类型为T的返回值 3. 异步方法(async Task)
将方法标记为async后,可以在方法中使用await关键字
await关键字会等待异步任务的结果,并获得结果
async + await 会将方法包装成状态机,await类似于检查点,MoveNext方法会被底层调用,从而切换状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 using System.ComponentModel.Design;using System.Runtime.CompilerServices;async Task Main (){ Helper.PrintThreadId("Main Start" ); await FooAsync(); Helper.PrintThreadId("Main End" ); } async Task FooAsync (){ Helper.PrintThreadId("Before" ); await Task.Delay(1000 ); Helper.PrintThreadId("After" ); } class Helper { private static int index = 1 ; public static void PrintThreadId (string ? message = null , [CallerMemberName] string ? memberName = null ) { var title = $"{index} :{memberName} " ; if (!string .IsNullOrEmpty(message)) title += $" @ {message} " ; Console.WriteLine(Environment.CurrentManagedThreadId.ToString() + $" - {title} " ); Interlocked.Increment(ref index); } }
一发即忘:调用一个异步方法,但是并不使用 await 或阻塞的方法去等待它的结果,无法观察任务的状态(是否完成,是否报错等)
简单任务
如何创建异步任务?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Task.Factory.StartNew() Task.Run() new Task + Task.Start()using System.Threading.Tasks;int HeavyJob (){ Thread.Sleep(1000 ); return 1 ; } async Task Main (){ var res = await Task.Factory.StartNew(HeavyJob); var res = await Task.Run(HeavyJob); Console.WriteLine($"After type: {res.GetType()} " ); }
如何开启多个任务?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 var inputs = Enumerable.Range(1 , 10 ) .ToArray(); var outputs = new List<int >();foreach (var input in inputs){ var output = await HeavyJob(input); outputs.Add(output); } var tasks = new List<Task<int >>();foreach (var input in inputs){ tasks.Add(HeavyJob(input)); } await Task.WhenAll(tasks);outputs = tasks.Select(x => x.Result).ToList(); Console.WriteLine("Outputs: " + string .Join(", " , outputs)); async Task<int > HeavyJob (int input ){ await Task.Delay(100 ); return input * 2 ; }
异步任务如何取消 CancellationTokenSource + CancellationToken OperationCanceledException & TaskCanceledException 推荐异步方法都带上CancellationToken这一个传参,可以不用但不能没有
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var cts = new CancellationTokenSource();try { var task = Task.Delay(100000 , cts.Token); Thread.Sleep(1000 ); cts.Cancel(); await task; } catch (OperationCanceledException){ Console.WriteLine("Operation was canceled." ); } finally { cts.Dispose(); }
常见误区
异步一定是多线程? 异步编程不必要多线程来实现-时间片轮换调度 比如可以在单个线程上使用异步I/O或事件驱动的编程模型(EAP) 单线程异步:自己定好计时器,到时间之前先去做别的事情 多线程异步:将任务交给不同的线程,并由自己来进行指挥调度
异步方法一定要写成 async Task? async 关键字只是用来配合await使用,从而将方法包装成为状态机 本质上乃然是Task,只不过提供了语法糖,并且行数体中可以直接return Task的泛型类型 接口中无法声明async Task
await 一定会切换同步上下文么? 在使用await关键字调用并等待一个异步任务时,异步方法不一定会立即来到新的线程上,如果await 了一个已经完成的任务(包括Task.Delay(0)),会直接获取结果。
异步可以全面取代多线程么? 异步编程与多线程有一定关系,但两者并不是可以完全相互代替的。
Rainbow Bubbles
I have seen through it, but not to the end.