当前位置:   article > 正文

C# Task和异步方法_c#异步方法和task的区别

c#异步方法和task的区别

ThreadPool中有若干数量的线程。当有任务需要处理时,会从线程池中获取一个空闲的线程来执行任务,任务执行完毕后线程不会销毁,而是被线程池回收以供后续任务使用。当线程池中所有的线程都被占用,又有新任务要处理时,线程池会新建一个线程来处理该任务。如果线程数量达到设置的最大值,任务会排队,等待其他任务释放线程后再执行。ThreadPool相对于Thread来说可以减少线程的创建,有效减小系统开销。但是ThreadPool不能控制线程的执行顺序,也不能获取线程池内线程取消/异常/完成的通知,即不能有效监控和控制线程池中的线程。因此NET4.0在ThreadPool的基础上推出了Task。Task拥有线程池的优点,同时也解决了使用线程池不易控制的弊端。

1.无返回值的Task的创建和执行

  1. using System;
  2. using System.Threading.Tasks;
  3. using System.Threading;
  4. namespace TaskDemo
  5. {
  6. class Program
  7. {
  8. static void Main(string[] args)
  9. {
  10. // 实例化一个Task,通过Start方法启动
  11. Task task = new Task(
  12. () =>
  13. {
  14. Thread.Sleep(1000);
  15. Console.WriteLine($"NEW实例化一个task,线程ID为{Thread.CurrentThread.ManagedThreadId}");
  16. }
  17. );
  18. task.Start();
  19. // Task.Factory.StartNew(Action action)创建和启动一个Task
  20. Task task2 = Task.Factory.StartNew(
  21. () =>
  22. {
  23. Thread.Sleep(500);
  24. Console.WriteLine($"Task.Factory.StartNew方式创建一个task,线程ID为{Thread.CurrentThread.ManagedThreadId}");
  25. });
  26. // Task.Run(Action action)将任务放在线程池队列,返回并启动一个Task
  27. Task task3 = Task.Run(
  28. () =>
  29. {
  30. Thread.Sleep(200);
  31. Console.WriteLine($"Task.Run方式创建一个task,线程ID为{Thread.CurrentThread.ManagedThreadId}");
  32. });
  33. Console.WriteLine("执行主线程");
  34. Console.Read();
  35. }
  36. }
  37. }

运行结果:

 2.用Task.Result获取返回值的Task的创建和执行

  1. namespace TaskDemo
  2. {
  3. class Program
  4. {
  5. static void Main(string[] args)
  6. {
  7. // 有返回值的启动task
  8. Task<string> task = new Task<string>(
  9. () =>
  10. {
  11. Thread.Sleep(1000);
  12. return $"NEW实例化一个task,线程ID为{Thread.CurrentThread.ManagedThreadId}";
  13. }
  14. );
  15. task.Start();
  16. // Task.Factory.StartNew(Action action)创建和启动一个Task
  17. Task<string> task2 = Task.Factory.StartNew(
  18. () =>
  19. {
  20. Thread.Sleep(3000);
  21. return $"Task.Factory.StartNew方式创建一个task,线程ID为{Thread.CurrentThread.ManagedThreadId}";
  22. });
  23. // Task.Run(Action action)将任务放在线程池队列,返回并启动一个Task
  24. Task<string> task3 = Task.Run(
  25. () =>
  26. {
  27. Thread.Sleep(2000);
  28. return $"Task.Run方式创建一个task,线程ID为{Thread.CurrentThread.ManagedThreadId}";
  29. });
  30. Console.WriteLine("执行主线程");
  31. Console.WriteLine(task.Result);
  32. Console.WriteLine(task2.Result);
  33. Console.WriteLine(task3.Result);
  34. Console.Read();
  35. }
  36. }
  37. }

运行结果:

可见Task.Result获取返回值时会阻塞线程。本例中,必须等到task2执行完成,获取到返回值后,才能继续执行task3。但是上面两个例子中的Task的执行都是异步的,不会阻塞主线程。

3.同步执行Task,会阻塞主线程

  1. namespace TaskDemo
  2. {
  3. class Program
  4. {
  5. static void Main(string[] args)
  6. {
  7. Task task = new Task(
  8. () =>
  9. {
  10. Thread.Sleep(1000);
  11. Console.WriteLine("执行Task结束");
  12. }
  13. );
  14. // 同步执行,会阻碍主线程
  15. task.RunSynchronously();
  16. Console.WriteLine("执行主线程");
  17. Console.Read();
  18. }
  19. }
  20. }

 运行结果:

 4.Task的阻塞方法(Wait/WaitAll/WaitAny)

使用Task Wait/WaitAll/WaitAny方法,实现阻塞线程

  • task.Wait()表示等待task执行完毕,类似于thread.Join()
  • task.WaitAll(Task[] tasks)表示只有所有的task都执行完毕再解除阻塞
  • task.WaitAny(Task[] tasks)表示只要有一个task执行完毕就解除阻塞
  1. Task task1 = new Task(
  2. () =>
  3. {
  4. Thread.Sleep(1000);
  5. Console.WriteLine("线程1执行完毕");
  6. });
  7. task1.Start();
  8. Task task2 = new Task(
  9. () =>
  10. {
  11. Thread.Sleep(2000);
  12. Console.WriteLine("线程2执行完毕");
  13. });
  14. task2.Start();
  15. // 阻塞主线程。task1和task2都执行完毕再执行主线程
  16. //task1.Wait();
  17. //task2.Wait();
  18. Task.WaitAll(new Task[] { task1, task2 });
  19. Console.WriteLine("主线程执行完毕");
  20. Console.Read();

运行结果:

 使用task1.Wait(); task2.Wait()可以达到同样的目的。如果把WaitAll改成WaitAny,则运行结果如下所示:

 

5.Task的延续操作(WhenAny/WhenAll/ContinueWith)

Wait/WaitAll/WaitAny方法返回值都是void,这些方法只是单纯的实现阻塞线程。使用WhenAny/WhenAll/ContinueWith方法可以让task执行完毕后,继续执行后续操作,这些方法执行完成返回一个task实例。

task.WhenAll(Task[] tasks)表示所有的task都执行完毕后再去执行后续的操作

task.WhenAny(Task[] tasks)表示任一task执行完毕后就开始执行后续操作

  1. Task task1 = new Task(
  2. () =>
  3. {
  4. Thread.Sleep(1000);
  5. Console.WriteLine("线程1执行完毕");
  6. });
  7. task1.Start();
  8. Task task2 = new Task(
  9. () =>
  10. {
  11. Thread.Sleep(2000);
  12. Console.WriteLine("线程2执行完毕");
  13. });
  14. task2.Start();
  15. Task.WhenAll(new Task[] { task1, task2 }).ContinueWith(
  16. (t) =>
  17. {
  18. Thread.Sleep(1000);
  19. Console.WriteLine("执行后续操作完毕");
  20. });
  21. Console.WriteLine("主线程执行完毕");
  22. Console.Read();

运行结果:

 WhenAll/WhenAny方法并不会阻塞主线程。也可以使用Task.Factory.ContinueWhenAll来实现

  1. Task.Factory.ContinueWhenAll(new Task[] { task1, task2 }, (t) =>
  2. {
  3. Thread.Sleep(1000);
  4. Console.WriteLine("执行后续操作完毕");
  5. });

6.Task的任务取消(CancellationTokenSource)

使用专门类CancellationTokenSource来取消任务执行。

  1. CancellationTokenSource source = new CancellationTokenSource();
  2. int index = 0;
  3. Task task1 = new Task(
  4. () =>
  5. {
  6. while (!source.IsCancellationRequested)
  7. {
  8. Thread.Sleep(1000);
  9. Console.WriteLine($"第{++index}次执行,线程运行中...");
  10. }
  11. });
  12. task1.Start();
  13. Console.WriteLine("主线程开始执行");
  14. Thread.Sleep(5000);
  15. source.Cancel();
  16. Console.WriteLine("主线程执行完毕");
  17. Console.Read();

运行结果:

 还可以使用source.CancelAfter(5000)实现5s后自动取消任务,即Thread.Sleep(5000); source.Cancel();这两条代码由source.CancelAfter(5000)取代。运行结果:

 注意这两次运行结果中,“主线程执行完毕”的区别。也可以通过source.Token.Register(Action action)注册取消任务触发的回调函数。

  1. CancellationTokenSource source = new CancellationTokenSource();
  2. source.Token.Register(
  3. () =>
  4. {
  5. Console.WriteLine("任务被取消后执行的操作");
  6. });
  7. int index = 0;
  8. Task task1 = new Task(
  9. () =>
  10. {
  11. Console.WriteLine($"task1的线程ID是{Thread.CurrentThread.ManagedThreadId}");
  12. while (!source.IsCancellationRequested)
  13. {
  14. Thread.Sleep(1000);
  15. Console.WriteLine($"第{++index}次执行,线程运行中...");
  16. }
  17. });
  18. task1.Start();
  19. Console.WriteLine($"主线程开始执行,主线程的ID是{Thread.CurrentThread.ManagedThreadId}");
  20. source.CancelAfter(5000);
  21. Console.WriteLine("主线程执行完毕");
  22. Console.Read();

运行结果:

7.异步方法(async/await)

  1. async static Task<string>GetContentAsync(string fileName)
  2. {
  3. Console.WriteLine($"当前线程ID是{Thread.CurrentThread.ManagedThreadId}");
  4. Console.WriteLine($"开始读取文件:{DateTime.Now}");
  5. Thread.Sleep(1000);
  6. using(StreamReader sr = new StreamReader(fileName))
  7. {
  8. string program = await sr.ReadToEndAsync();
  9. Console.WriteLine($"读取文件结束:{DateTime.Now}");
  10. return program;
  11. }
  12. }
  13. // 同步读取文件内容
  14. static string GetContent(string fileName)
  15. {
  16. using (StreamReader sr = new StreamReader(fileName))
  17. {
  18. string program = sr.ReadToEnd();
  19. return program;
  20. }
  21. }
  22. static void Main(string[] args)
  23. {
  24. string path = @"D:\Demos\TaskDemo\postdata.txt";
  25. Console.WriteLine($"主线程ID是{Thread.CurrentThread.ManagedThreadId}");
  26. Console.WriteLine($"主程序执行开始:{DateTime.Now}");
  27. string content = GetContentAsync(path).Result;
  28. Console.WriteLine($"主程序输入结果:{content}");
  29. Console.WriteLine($"主程序执行结束:{DateTime.Now}");
  30. Console.Read();
  31. }

 运行结果:

 主程序等待GetContentAsync方法执行完毕后,获取到返回值后才继续执行。这说明,如果调用方法要从调用中获取一个T类型的值,异步方法的返回类型必须是Task<T>,而且调用会获取到返回值后才会继续执行下去。如果仅仅是调用一下异步方法,不和异步方法做其他交互,则将异步方法签名返回值为void,这种调用形式也被称为“调用并忘记”。

  1. async static void GetContentAsync(string fileName)
  2. {
  3. Console.WriteLine($"当前线程ID是{Thread.CurrentThread.ManagedThreadId}");
  4. Console.WriteLine($"开始读取文件:{DateTime.Now}");
  5. Thread.Sleep(1000);
  6. using(StreamReader sr = new StreamReader(fileName))
  7. {
  8. string program = await sr.ReadToEndAsync();
  9. Console.WriteLine($"读取文件结束:{DateTime.Now}");
  10. }
  11. }
  12. static void Main(string[] args)
  13. {
  14. string path = @"D:\Demos\TaskDemo\postdata.txt";
  15. Console.WriteLine($"主线程ID是{Thread.CurrentThread.ManagedThreadId}");
  16. Console.WriteLine($"主程序执行开始:{DateTime.Now}");
  17. GetContentAsync(path);
  18. Console.WriteLine($"主程序执行结束:{DateTime.Now}");
  19. Console.Read();
  20. }

运行结果:

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/104508
推荐阅读
相关标签
  

闽ICP备14008679号