学海无涯

导航

统计

多线程 Task

Net Framework4.0引入了一个新的关于异步操作的API,它叫做.任务并行库( Task Parallel Library,简称TPL), .Net Framework 4.5版对该API进行了轻微的改进,使用更简单。TPL可被认为是线程池之上的又一个抽象层,其对程序员隐藏了与线程池交互的底层代码,并提供了更方便的细粒度的APL, TPL的核心概念是任务。一个任务代表了一个异步操作,该操作可以通过多种方式运行,可以使用或不使用独立线程运行。

一个任务可以通过多种方式和其他任务组合起来。例如,可以同时启动多个任务,等待所有任务完成,然后运行一个任务对之前所有任务的结果进行一些计算。TPL与之前的模式相比,其中一个关键优势是其具有用于组合任务的便利的API,

处理任务中的异常结果有多种方式。由于一个任务可能会由多个其他任务组成,这些任,务也可能依次拥有各自的子任务,所以有一个AggregateException的概念。这种异常可以捕获底层任务内部的所有异常,并允许单独处理这些异常。

  而且,最后但并不是最不重要的, C# 5.0已经内置了对TPL的支持,允许我们使用新的 await和async关键字以平滑的、舒服的方式操作任务。

创建任务

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
namespace ConsoleAppTest
{
    internal class Program
    {
        static void Main(string[] args)
        {
           var t1=new Task(()=> TaskMethod("Task 1"));//仅当调用Start()方法时才会执行
            var t2 = new Task(() => TaskMethod("Task 2"));
            t2.Start();
            t1.Start();
            Task.Run(() => TaskMethod("Task 3"));//立即执行,是StartNew 的快捷方式
            Task.Factory.StartNew(() => TaskMethod("Task 4"));//立即执行
            //标记任务长时间运行,结果任务将不会使用线程池,而在单独的线程中运行,
            //然而,根据运行该任务的当前的任务调度程序( task scheduler)运行方式有可能不同。
            Task.Factory.StartNew(()=>TaskMethod("Task 5"),TaskCreationOptions.LongRunning);
            Thread.Sleep(TimeSpan.FromSeconds(1));
 
            Console.ReadKey();
        }
 
        static void TaskMethod(string name)
        {
            Console.WriteLine("任务: {0} 线程ID: {1}. 是否在线程池中: {2}",
                name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
        }
    }
}

  

注意:每次运行,任务的执行顺序可能不一样,Task 5 标记任务长时间运行,结果任务将不会使用线程池,而在单独的线程中运行,

使用任务执行基本操作

获取任务的结果

1
int result = task.Result;//得到任务执行后的结果

同步运行

1
task.RunSynchronously();//同步运行

  

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
///<summary>
    /// 使用任务执行基本的操作
    /// </summary>
    internal class TaskRunBasicOperate
    {
        public static void StartTask()
        {
            TaskMethod("Main Thread Task");
 
            Task<int> task = CreateTask("Task 1");
            task.Start();
            //该任务会被放置在线程池中,并且主线程会等待,直到任务返回前一直处于阻塞状态。
            int result = task.Result;//得到任务执行后的结果
 
            Console.WriteLine("Result is: {0}", result);
 
            task = CreateTask("Task 2");
           // 该任务会运行在主线程中,该任务的输出与直接调用TaskMethod的输出完全一样。
           // 这是个非常好的优化,可以避免使用线程池来执行非常短暂的操作。
            task.RunSynchronously();//同步运行
            result = task.Result;
            Console.WriteLine("Result is: {0}", result);
 
            //没有调用 task.Result 所有不会阻塞主线程
            task = CreateTask("Task 3");
            Console.WriteLine(task.Status);
            task.Start();
            
            while (!task.IsCompleted)
            {
                Console.WriteLine(task.Status);
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
            }
 
            Console.WriteLine(task.Status);
            result = task.Result;
            Console.WriteLine("Result is: {0}", result);
        }
        /// <summary>
        /// 返回一个整数的任务
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        static Task<int> CreateTask(string name)
        {
            return new Task<int>(()=>TaskMethod(name));
        }
        static int TaskMethod(string name)
        {
            Console.WriteLine("Task [{0}] is running on a thread id [{1}]. Is thread pool thread: [{2}]",
                name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
            Thread.Sleep(TimeSpan.FromSeconds(2));
            return 42;
        }
    }
}

 

组合任务

设置相互依赖的任务。我们将学习如何创建一个任务,使其在父任务完成后才会被运行。另外,将探寻为非常短暂的任务节省线程开销的可能性。

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
namespace ConsoleAppTest.TaskTest
{
    /// <summary>
    /// 组合任务
    /// </summary>
    internal class AssembleTask
    {
        public static void StartTask()
        {
            var firstTask = new Task<int>(() => TaskMethod("First Task", 3));
            var secondTask = new Task<int>(() => TaskMethod("Second Task", 2));
            //当前任务完成后的,后续操作,参数为前一个任务返回的结果
            firstTask.ContinueWith(
           t => Console.WriteLine("The first answer is {0}. Thread id {1}, is thread pool thread: {2}",
               t.Result, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread),
           TaskContinuationOptions.OnlyOnRanToCompletion);
 
            firstTask.Start();
            secondTask.Start();
            //等待上面两个任务运行完成
            Thread.Sleep(TimeSpan.FromSeconds(4));
            //同步执行,在主线程中运行
            Task continuation = secondTask.ContinueWith(
           t => Console.WriteLine("The second answer is {0}. Thread id {1}, is thread pool thread: {2}",
               t.Result, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread),
           TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously);
            //后后续操作,这些方法是C# 5.0语言中异步机制中的方法。
            continuation.GetAwaiter().OnCompleted(
                () => Console.WriteLine("Continuation Task Completed! Thread id {0}, is thread pool thread: {1}",
                    Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread));
 
            Thread.Sleep(TimeSpan.FromSeconds(2));
            Console.WriteLine();
            //带子任务的任务,仅当所有子任务结束工作,父任务才会完成。
            firstTask = new Task<int>(() =>
            {
                //子任务,将子任务附加到父任务,
                var innerTask = Task.Factory.StartNew(() => TaskMethod("Second Task", 5), TaskCreationOptions.AttachedToParent);
                //子任务的后续操作
                innerTask.ContinueWith(t => TaskMethod("Third Task", 2), TaskContinuationOptions.AttachedToParent);
                return TaskMethod("First Task", 2);//父任务
            });
 
            firstTask.Start();
 
            while (!firstTask.IsCompleted)
            {
                Console.WriteLine(firstTask.Status);
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
            }
            Console.WriteLine(firstTask.Status);
 
            Thread.Sleep(TimeSpan.FromSeconds(10));
        }
        static int TaskMethod(string name, int seconds)
        {
            Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}",
                name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
            Thread.Sleep(TimeSpan.FromSeconds(seconds));
            return 42 * seconds;
        }
 
    }
}

 

 异步操作实现取消流程

 如何正确的使用取消标志,以及在任务真正运行前如何得知其是否被取消。

 

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
33
34
35
36
37
38
39
40
internal class CancellationTokenTest
    {
        public static void StartTask()
        {
            var cts = new CancellationTokenSource();
            var longTask = new Task<int>(() => TaskMethod("Task 1", 10, cts.Token), cts.Token);
            Console.WriteLine(longTask.Status);
            cts.Cancel();
            Console.WriteLine(longTask.Status);
            Console.WriteLine("First task has been cancelled before execution");
            cts = new CancellationTokenSource();
            longTask = new Task<int>(() => TaskMethod("Task 2", 10, cts.Token), cts.Token);
            longTask.Start();
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
                Console.WriteLine(longTask.Status);
            }
            cts.Cancel();
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
                Console.WriteLine(longTask.Status);
            }
 
            Console.WriteLine("A task has been completed with result {0}.", longTask.Result);
        }
 
        private static int TaskMethod(string name, int seconds, CancellationToken token)
        {
            Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}",
                name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
            for (int i = 0; i < seconds; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));
                if (token.IsCancellationRequested) return -1;
            }
            return 42 * seconds;
        }
    }

 

 处理任务中的异常

 

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
internal class ExceptionTask
   {
       public static void StartTask()
       {
           Task<int> task;
           //try
           //{
           //    task = Task.Run(() => TaskMethod("Task 1", 2));
           //    int result = task.Result;
           //    Console.WriteLine("Result: {0}", result);
           //}
           //catch (Exception ex)
           //{
           //    Console.WriteLine("Exception caught: {0}", ex);
           //}
           //Console.WriteLine("----------------------------------------------");
           //Console.WriteLine();
           //无需封装异常,因为TPL基础设施会提取该异常。
           //如果只有一个底层任务,那么一次只能获取一个原始异常,这种设计非常合适。
           try
           {
               task = Task.Run(() => TaskMethod("Task 2", 2));
               int result = task.GetAwaiter().GetResult();//推荐,仅当只有一个任务时
               Console.WriteLine("Result: {0}", result);
           }
           catch (Exception ex)
           {
               Console.WriteLine("Exception caught: {0}", ex);
           }
           Console.WriteLine("----------------------------------------------");
           Console.WriteLine();
 
           // 使用后续操作来处理异常,只有之前,的任务完成前有异常时,该后续操作才会被执行
           //结果打印出了AggregateException,其内部封装了两个任务抛出的异常。
           //var t1 = new Task<int>(() => TaskMethod("Task 3", 3));
           //var t2 = new Task<int>(() => TaskMethod("Task 4", 2));
           //var complexTask = Task.WhenAll(t1, t2);
           //
           //var exceptionHandler = complexTask.ContinueWith(t =>
           //        Console.WriteLine("Exception caught: {0}", t.Exception),
           //        TaskContinuationOptions.OnlyOnFaulted
           //    );
           //t1.Start();
           //t2.Start();
 
           Thread.Sleep(TimeSpan.FromSeconds(5));
       }
 
       static int TaskMethod(string name, int seconds)
       {
           Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}",
               name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
           Thread.Sleep(TimeSpan.FromSeconds(seconds));
           throw new Exception("Boom!");
           return 42 * seconds;
       }
   }

并行运行任务

同时运行多个异步任务。我们将学习当所有任务都完成或任意一个任务,完成了工作时,如何高效地得到通知。

 

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/// <summary>
   /// 并行运行任务
   /// </summary>
   internal class MultiTask
   {
       
       public static void StartTask()
       {
           var firstTask = new Task<int>(() => TaskMethod("First Task", 3));
           var secondTask = new Task<int>(() => TaskMethod("Second Task", 2));
           var whenAllTask = Task.WhenAll(firstTask, secondTask);
           //该任务将会在所有任务完成后运行
           //该任务的结果提供了一个结果数组,第一个元素是第.个任务的结果,第二个元素是第二个任务的结果,以此类推。
           whenAllTask.ContinueWith(t =>
               Console.WriteLine("The first answer is {0}, the second is {1}", t.Result[0], t.Result[1]),
               TaskContinuationOptions.OnlyOnRanToCompletion
               );
 
           firstTask.Start();
           secondTask.Start();
 
           Thread.Sleep(TimeSpan.FromSeconds(4));
 
           var tasks = new List<Task<int>>();
           for (int i = 1; i < 4; i++)
           {
               int counter = i;
               var task = new Task<int>(() => TaskMethod(string.Format("Task {0}", counter), counter));
               tasks.Add(task);
               task.Start();
           }
           //获取任务的完成进展情况或在运行任务时使用超时,都可以使用Task.WhenAny方法。
           //例如,我们等待一组任务运行,并且使用其中一个任务用来记录是否超时。如果该任务先完成,
           //则只需取消掉其他还未完成的任务。
           while (tasks.Count > 0)
           {
               //等待这些任务中的任何一个完成,当有一个完成任务后,从列表中移除该任务并继续等待其他任务完成
               var completedTask = Task.WhenAny(tasks).Result;
               tasks.Remove(completedTask);
               Console.WriteLine("A task has been completed with result {0}.", completedTask.Result);
           }
 
           Thread.Sleep(TimeSpan.FromSeconds(1));
 
       }
 
       static int TaskMethod(string name, int seconds)
       {
           Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}",
               name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
           Thread.Sleep(TimeSpan.FromSeconds(seconds));
           return 42 * seconds;
       }
   }

  

 

 

 

 

 

posted on   宁静致远.  阅读(38)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 上周热点回顾(2.17-2.23)
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
点击右上角即可分享
微信分享提示