C#中的异步多线程补充1
1、都是同步情况下的示例
static int CPUMission(int val) { Console.WriteLine($"CPU Start:{Thread.CurrentThread.ManagedThreadId.ToString()}"); for (long i = 0; i < 100_000_000; i++) ; Console.WriteLine($"CPU End:{Thread.CurrentThread.ManagedThreadId.ToString()}"); return val; } static int IOMission(int val) { Console.WriteLine($"IO Start:{Thread.CurrentThread.ManagedThreadId.ToString()}"); Thread.Sleep(3000); Console.WriteLine($"IO End:{Thread.CurrentThread.ManagedThreadId.ToString()}"); return val; } static void Main(string[] args) { Console.WriteLine($"Main Thread Start:{Thread.CurrentThread.ManagedThreadId.ToString()}"); //Task<int> t1 = Task.Run(() => IOMission(3)); int t1 = IOMission(3); int s1 = CPUMission(3); Console.WriteLine($"Main Thread End:{Thread.CurrentThread.ManagedThreadId.ToString()}"); Console.ReadLine(); }
输出:同步按顺序依次执行,直观,简单
Main Thread Start:1
IO Start:1
IO End:1
CPU Start:1
CPU End:1
Main Thread End:1
2、启用线程池线程处理IO任务的情况
static void Main(string[] args) { Console.WriteLine($"Main Thread Start:{Thread.CurrentThread.ManagedThreadId.ToString()}"); Task<int> t1 = Task.Run(() => IOMission(3)); //int t1 = IOMission(3); int s1 = CPUMission(3); Console.WriteLine($"Main Thread End:{Thread.CurrentThread.ManagedThreadId.ToString()}"); Console.ReadLine(); }
输出:
Main Thread Start:1
CPU Start:1
IO Start:4
CPU End:1
Main Thread End:1
IO End:4
可以发现,因为IO任务是通过另开线程执行,所以IO任务和CPU任务之间的顺序就是不可预测的了。
3、现在调用结果
static void Main(string[] args) { Console.WriteLine($"Main Thread Start:{Thread.CurrentThread.ManagedThreadId.ToString()}"); Task<int> t1 = Task.Run(() => IOMission(3)); //int t1 = IOMission(3); int s1 = CPUMission(3); Console.WriteLine(t1); Console.WriteLine(s1); Console.WriteLine($"Main Thread End:{Thread.CurrentThread.ManagedThreadId.ToString()}"); Console.ReadLine(); }
输出:
Main Thread Start:1
CPU Start:1
IO Start:4
CPU End:1
System.Threading.Tasks.Task`1[System.Int32]
3
Main Thread End:1
IO End:4
可以看到,虽然调用WriteLine方法打印t1,但t1的返回值是个Task<int>,并不是想要拿到的值,并且此时IO所在线程仍然不是同步的。(IO End在Main Thread End之后出现)
4、调用Task.Result
把Console.WriteLine(t1)修改成Console.WriteLine(t1.Result);
输出结果如下:
Main Thread Start:1
CPU Start:1
IO Start:4
CPU End:1
IO End:4
3
3
Main Thread End:1
可以看到取得了输出结果,并且按照同步地方式执行了后面的语句,也就是说,Task.Result会阻塞当前线程,让其等待Task.Result取得结果,但在之前,异步和同步方法执行顺序仍然是不可预测的。
5、异步方法
通过调用一个异步方法来调用IOMission
static int IOMission(int val) { Console.WriteLine($"IO Start:{Thread.CurrentThread.ManagedThreadId.ToString()}"); Thread.Sleep(3000); Console.WriteLine($"IO End:{Thread.CurrentThread.ManagedThreadId.ToString()}"); return val; } static async Task<int> UseIOMission(int val) { int result = await Task.Run(() => IOMission(val)); return result; }
调用
static void Main(string[] args) { Console.WriteLine($"Main Thread Start:{Thread.CurrentThread.ManagedThreadId.ToString()}"); Task<int> t1 = UseIOMission(3); //Task<int> t1 = Task.Run(() => IOMission(3)); //int t1 = IOMission(3); int s1 = CPUMission(3); Console.WriteLine(t1.Result); Console.WriteLine(s1); Console.WriteLine($"Main Thread End:{Thread.CurrentThread.ManagedThreadId.ToString()}"); Console.ReadLine(); }
输出结果:
Main Thread Start:1
IO Start:4
CPU Start:1
CPU End:1
IO End:4
3
3
Main Thread End:1
这样可能还看不到区别,但如果在异步方法中打印一下线程Id
static async Task<int> UseIOMission(int val) { Console.WriteLine($"UseIO Start:{Thread.CurrentThread.ManagedThreadId.ToString()}"); int result = await Task.Run(() => IOMission(val)); Console.WriteLine($"UseIO End:{Thread.CurrentThread.ManagedThreadId.ToString()}"); return result; }
Main Thread Start:1
UseIO Start:1
IO Start:4
CPU Start:1
CPU End:1
IO End:4
UseIO End:4
3
3
Main Thread End:1
会看到await前后线程Id会改变,也就是说,await Task.Run是异步开启,而Task.Run本身是同步地开启新线程,这一步比较细节,不一定说的正确,会在后面进一步补充修正。
补充一个小例子:
private void SyncExecuteLongTimeMission() { Thread.Sleep(3000); MessageBox.Show("Mission End"); } private async void AsyncExecuteLongTimeMission() { await Task.Run(() => SyncExecuteLongTimeMission()); MessageBox.Show("Async End"); } private void NoAsync_Click(object sender, EventArgs e) { SyncExecuteLongTimeMission(); } private void Async_Click(object sender, EventArgs e) { AsyncExecuteLongTimeMission(); } private void OnlyTask_Click(object sender, EventArgs e) { Task.Run(() => SyncExecuteLongTimeMission()); MessageBox.Show("Task.Run End"); }
使用winForm来查看,单纯的Task.Run任务也不会阻塞UI主线程,但会先行显示Task.Run End然后才是Mission End,但如果是在async方法中await Task.Run,那么不仅不会阻塞主线程,还会先Mission End然后才是Async End。
也就是说,async await提供了一种以同步方式调用异步方法的方式,它不仅不会阻塞主线程,还会以同步调用的感觉来返回值,尽管它实际上是异步方法。