Task C# 多线程和异步模型 TPL模型
Task,异步,多线程简单总结
1,如何把一个异步封装为Task异步
Task.Factory.FromAsync
对老的一些异步模型封装为Task
TaskCompletionSource
更通用,在回调中只要SetResult()一下就表示Task结束了,用它可以将各种异步回调封装为Task
2,一个可以await的可以返回Task的Async结尾的异步方法从哪里开始进入另一个线程的
如果是对BeginXXX EndXXX的APM异步模型封装的,从进入XXXAsync方法后直到BeginXXX前还在调用方法的线程内,然后无阻塞的等待回调,当等打完后,就相当于在回调中了,此时可能进入了另一个线程,其他也是类似的,从底层方法异步的地方开始异步的,而不是一进入方法就到了另一个线程了,所以进入方法后写很多CPU密集程序是会阻塞的
3,如何立刻扔到另一个线程
Task.Run或者Task.Factory.StartNew可以直接从另一个线程开始,可以直接把XXXAsync方法扔进去
4,一个约定
纯C#框架或者类库提供的Async结尾的可await方法,里面一定有无阻塞异步的实现,否则没必要搞成异步的,就比如newtonsoft.json的异步序列化方法被标识为已过时。
5,Task外面如何告诉Task该取消了
CancellationTokenSource
其实和一个Flag差不多,只不过封装了一些方法以异常类
6,很多情况下要先学会同步才能并发
7,Task.Run Task.Start Task.Factory.StartNew 等都是使用线程池的线程
8,IO异步底层为IRP消息,通常是和硬件交互所使用的消息机制,当然那是驱动层的事情,IO异步当然也就是无阻塞的,等IRP消息回来就是回调
9,UI线程
最终会渲染界面的代码一定要在UI线程执行,可以使用比如winform的control.Invoke ,wpf的Dispatcher
一旦开始并发了,还有很多很多的坑
【C#】43. TPL基础——Task初步
从这篇文章开始,我想直接进入关于Task的一些内容,有时间再回顾Threadpool的相关内容。
我一开始接触Task就觉得他和Thread很像,都是开新的线程。但是两者有很多区别,其中比较明显的是:Task创建的是线程池任务,而Thread默认创建的是前台任务。
同Thread一样,Task可以使用lambda表达式来构造action,作为Task的构造函数参数。如下:
1、先定义一个函数TaskMethod,他接受name作为字符串参数。
- static void TaskMethod(string name)
- {
- Console.WriteLine("Task {0} 运行在线程id为{1}的线程上. 是否是线程池中线程?:{2}",
- name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
- }
2、新建Task实例,并且Start():
- var t1 = new Task(() => TaskMethod("Task 1"));
- t1.Start();
因为Task接受无参数和返回值的Action作为构造器参数,因此此处可以使用如上的lambda表达式,在表达式中传参"Task 1"。
运行结果如下:
可以很明显的看出,该Task(t1)运行在线程池中。
必须要指出的是,线程池一般只运行执行时间较短的异步操作,需要长时间执行的操作尽量不要使用线程池。
除了上面这种开Task的方法,还有两种常见的用法,分别是 Task.Run() 和 Task.Factory.StartNew()。两者的区别在于后者能传入一些额外参数,以丰富Task的运行选项。例如:
- Task.Run(() => TaskMethod("Task 3"));
- Task.Factory.StartNew(() => TaskMethod("Task 4"));
- Task.Factory.StartNew(() => TaskMethod("Task 5"), TaskCreationOptions.LongRunning);
第一句直接调用静态方法Task.Run(),lambda表达式作为参数(Action),不需要再调用Start()方法,立即执行。
第二句则使用了Task.Factory.StartNew()默认方法,最后一句加入了选项“LongRunning”,意味着该任务将长时间运行,因此他不是在线程池中执行。
结果如下:
注意:Task的运行有一定的随机性,开始次序会有变化!
22 C# 第十八章 TPL 并行编程
一: Task 线程的基本使用
关于Action委托: 它是 .Net 定义的一种委托类型。
public delegate void Action(): 封装一个方法,该方法不具有参数并且不返回值。
public delegate void Action<in T>(T obj): 封装一个方法,该方法只有一个参数并且不返回值。
... ...
可以使用此委托以参数形式传递方法,而不用显式声明自定义的委托。 封装的方法必须与此委托定义的方法签名相对应。
http://msdn.microsoft.com/zh-cn/library/vstudio/system.action.aspx
光看语言描述理解起来有点费劲。看MSDN给的例子,把自定义的委托和Action比较一下就清楚多了。
两个小例子:
使用委托的例子
- using System;
- using System.Windows.Forms;
- public delegate void ShowValue();
- public class Name
- {
- private string instanceName;
- public Name(string name)
- {
- this.instanceName = name;
- }
- public void DisplayToConsole()
- {
- Console.WriteLine(this.instanceName);
- }
- public void DisplayToWindow()
- {
- MessageBox.Show(this.instanceName);
- }
- }
- public class testTestDelegate
- {
- public static void Main()
- {
- Name testName = new Name("Koani");
- ShowValue showMethod = testName.DisplayToWindow;
- showMethod();
- }
- }
使用 action的例子
- using System;
- using System.Windows.Forms;
- public class Name
- {
- private string instanceName;
- public Name(string name)
- {
- this.instanceName = name;
- }
- public void DisplayToConsole()
- {
- Console.WriteLine(this.instanceName);
- }
- public void DisplayToWindow()
- {
- MessageBox.Show(this.instanceName);
- }
- }
- public class testTestDelegate
- {
- public static void Main()
- {
- Name testName = new Name("Koani");
- Action showMethod = testName.DisplayToWindow;
- showMethod();
- }
- }
这里把Action 看成了一种特殊的委托,没有参数也没有返回值,这样理解起来就简单了。
关于 Task 的简单介绍
更确切的说它是把线程的概念抽象出来了。这里它更关心的是线程中工作的内容。而把创建线程,重用线程或释放线程等平台相关的工作留给了系统。它更贴近与System.Threading.ThreadPool。一个由系统管理的ThreadPool。
使用 Task 创建线程
一个简单的线程使用的例子
http://msdn.microsoft.com/zh-cn/library/vstudio/1h2f2459.aspx
- using System;
- using System.Threading;
- using System.Threading.Tasks;
- class StartNewDemo
- {
- static void Main()
- {
- Action<object> action = (object obj) =>
- {
- Console.WriteLine("Task={0}, obj={1}, Thread={2}", Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId);
- };
- // Construct an unstarted task
- Task t1 = new Task(action, "alpha");
- // Cosntruct a started task
- Task t2 = Task.Factory.StartNew(action, "beta");
- // Block the main thread to demonstate that t2 is executing
- t2.Wait();
- // Launch t1
- t1.Start();
- Console.WriteLine("t1 has been launched. (Main Thread={0})", Thread.CurrentThread.ManagedThreadId);
- // Wait for the task to finish.
- // You may optionally provide a timeout interval or a cancellation token
- // to mitigate situations when the task takes too long to finish.
- t1.Wait();
- // Construct an unstarted task
- Task t3 = new Task(action, "gamma");
- // Run it synchronously
- t3.RunSynchronously();
- // Although the task was run synchrounously, it is a good practice to wait for it which observes for
- // exceptions potentially thrown by that task.
- t3.Wait();
- Console.ReadKey();
- }
- }
程序说明:
API: Task.Factory
创建任务并立即启动任务。
创建在一组任务中的任意或全部任务完成后启动的任务延续项
创建表示开始/结束方法对的任务,这些方法采用异步编程模型。
Task.RunSynchronously
任务只可以启动并运行一次。 再次计划任务的尝试将导致异常。通过 RunSynchronously 执行的任务将与当前 TaskScheduler 关联。
如果目标计划程序不支持当前线程上运行此任务,则在计划程序上计划执行该任务,当前线程会阻塞,直到该任务完成执行。
从线程中返回结果
线程常用的成员
Status: 此任务实例的当前状态 TaskStatus(http://msdn.microsoft.com/zh-cn/library/vstudio/system.threading.tasks.taskstatus.aspx)
IsCompleted: true ,如果任务已完成;否则 false。它不管任务是否出错。
Id: System.Int32类型, 系统分配给此任务实例的一个整数。任务 ID 分配在需要时并不一定表示在任务实例创建的顺序。
AsyncState 它允许跟踪额外的数据。例如,假定多个任务要计算一个List<T>的值。为了将结果放到列表中正确的位置,一个办法是将准备包含结果的那个列表索引存储到AsyncState中。这样一来,在任务结束后,代码可使用AsyncState先转型为int 访问列表中特定索引的位置。这个理解的还不是很透彻,回头应该找个例子看看。
ContinueWith(): 创建一个在目标 Task 完成时异步执行的延续任务。
一个实例
- using System;
- using System.Collections.Generic;
- using System.Threading;
- using System.Threading.Tasks;
- // Demonstrates how to associate state with task continuations.
- class ContinuationState
- {
- // Simluates a lengthy operation and returns the time at which
- // the operation completed.
- public static DateTime DoWork()
- {
- // Simulate work by suspending the current thread
- // for two seconds.
- Console.WriteLine("Thread = {0} sleep 2 seconds", Task.CurrentId);
- Thread.Sleep(2000);
- // Return the current time.
- return DateTime.Now;
- }
- static void Main(string[] args)
- {
- Action action = () =>
- {
- DoWork();
- };
- Task<DateTime> t = new Task<DateTime>(DoWork);
- t.Start();
- Console.WriteLine("Date = {0}", t.Result);
- t.Wait();
- Console.ReadKey();
- }
- }
二: Task上的未处理异常
关注点是如何从一个不同的线程中引发的未处理的异常。在Task执行期间产生的未处理的异常会被禁止(suppressed),直到调用某个任务完成(task complete)成员: Wait(), Result, Task.WaitAll(),或者Task.WaitAny()。上述每个成员都会引发任务执行期间发生的任何未处理的异常。Wait引发异常的例子
- using System;
- using System.Threading;
- using System.Threading.Tasks;
- namespace Thread_Task_Sample_Exception_Simple
- {
- class Program
- {
- static void Main(string[] args)
- {
- Action<object> action = (object obj) =>
- {
- Console.WriteLine("Task={0}, obj={1}, Thread={2} Throw Exception", Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId);
- throw (new Exception());
- };
- // Cosntruct a started task
- Task t = Task.Factory.StartNew(action, "A");
- for (int i = 0; i < 50; i++)
- {
- Console.Write(".");
- Thread.Sleep(200);
- }
- Console.WriteLine();
- try
- {
- // Block the main thread to demonstate that t2 is executing
- t.Wait();
- }
- catch (Exception ex)
- {
- Console.WriteLine("Task.Wait cause an exception, We caught it");
- }
- Console.ReadKey();
- }
- }
- }
使用 ContinueWith ()
- using System;
- using System.Threading;
- using System.Threading.Tasks;
- class ContinuationSimpleDemo
- {
- // Demonstrated features:
- // Task.Factory
- // Task.ContinueWith()
- // Task.Wait()
- // Expected results:
- // A sequence of three unrelated tasks is created and executed in this order - alpha, beta, gamma.
- // A sequence of three related tasks is created - each task negates its argument and passes is to the next task: 5, -5, 5 is printed.
- // A sequence of three unrelated tasks is created where tasks have different types.
- // Documentation:
- static void Main()
- {
- Action<string> action =
- (str) =>
- Console.WriteLine("Task={0}, str={1}, Thread={2}", Task.CurrentId, str, Thread.CurrentThread.ManagedThreadId);
- // Creating a sequence of action tasks (that return no result).
- Console.WriteLine("Creating a sequence of action tasks (that return no result)");
- Task.Factory.StartNew(() => action("alpha"))
- .ContinueWith(antecendent => action("beta")) // Antecedent data is ignored
- .ContinueWith(antecendent => action("gamma"))
- .Wait();
- Func<int, int> negate =
- (n) =>
- {
- Console.WriteLine("Task={0}, n={1}, -n={2}, Thread={3}", Task.CurrentId, n, -n, Thread.CurrentThread.ManagedThreadId);
- return -n;
- };
- // Creating a sequence of function tasks where each continuation uses the result from its antecendent
- Console.WriteLine("\nCreating a sequence of function tasks where each continuation uses the result from its antecendent");
- Task<int>.Factory.StartNew(() => negate(5))
- .ContinueWith(antecendent => negate(antecendent.Result)) // Antecedent result feeds into continuation
- .ContinueWith(antecendent => negate(antecendent.Result))
- .Wait();
- // Creating a sequence of tasks where you can mix and match the types
- Console.WriteLine("\nCreating a sequence of tasks where you can mix and match the types");
- Task<int>.Factory.StartNew(() => negate(6))
- .ContinueWith(antecendent => action("x"))
- .ContinueWith(antecendent => negate(7))
- .Wait();
- Console.ReadKey();
- }
- }
使用ContinueWith 如果在程序中出现异常,后面后续的处理函数可以帮助程序把信息清理干净。
三: 取消任务
野蛮终止的问题:在.Net3.5 或之前的版本,其实并没有怎么支持线程的取消,相反采取的是一种中断的方式。.Net4 中基于PLINQ和TPL的API只支持一种取消请求的方式,协作式取消 -- 目标Task可自行决定是否满足取消请求。取消任务的一个实例代码:
- using System;
- using System.Collections.Concurrent;
- using System.Threading;
- using System.Threading.Tasks;
- public class Example
- {
- public static void Main()
- {
- var tokenSource = new CancellationTokenSource();
- var token = tokenSource.Token;
- // Store references to the tasks so that we can wait on them and
- // observe their status after cancellation.
- Task t;
- var tasks = new ConcurrentBag<Task>();
- Console.WriteLine("Press any key to begin tasks...");
- Console.WriteLine("To terminate the example, press 'c' to cancel and exit...");
- Console.ReadKey();
- Console.WriteLine();
- // Request cancellation of a single task when the token source is canceled.
- // Pass the token to the user delegate, and also to the task so it can
- // handle the exception correctly.
- t = Task.Factory.StartNew(() => DoSomeWork(1, token), token);
- Console.WriteLine("Task {0} executing", t.Id);
- tasks.Add(t);
- // Request cancellation of a task and its children. Note the token is passed
- // to (1) the user delegate and (2) as the second argument to StartNew, so
- // that the task instance can correctly handle the OperationCanceledException.
- t = Task.Factory.StartNew(() =>
- {
- // Create some cancelable child tasks.
- Task tc;
- for (int i = 3; i <= 10; i++)
- {
- // For each child task, pass the same token
- // to each user delegate and to StartNew.
- tc = Task.Factory.StartNew(iteration => DoSomeWork((int)iteration, token), i, token);
- Console.WriteLine("Task {0} executing", tc.Id);
- tasks.Add(tc);
- // Pass the same token again to do work on the parent task.
- // All will be signaled by the call to tokenSource.Cancel below.
- DoSomeWork(2, token);
- }
- }, token);
- Console.WriteLine("Task {0} executing", t.Id);
- tasks.Add(t);
- // Request cancellation from the UI thread.
- if (Console.ReadKey().KeyChar == 'c')
- {
- tokenSource.Cancel();
- Console.WriteLine("\nTask cancellation requested.");
- // Optional: Observe the change in the Status property on the task.
- // It is not necessary to wait on tasks that have canceled. However,
- // if you do wait, you must enclose the call in a try-catch block to
- // catch the TaskCanceledExceptions that are thrown. If you do
- // not wait, no exception is thrown if the token that was passed to the
- // StartNew method is the same token that requested the cancellation.
- }
- try
- {
- Task.WaitAll(tasks.ToArray());
- }
- catch (AggregateException e)
- {
- Console.WriteLine("\nAggregateException thrown with the following inner exceptions:");
- // Display information about each exception.
- foreach (var v in e.InnerExceptions)
- {
- if (v is TaskCanceledException)
- Console.WriteLine(" TaskCanceledException: Task {0}",
- ((TaskCanceledException)v).Task.Id);
- else
- Console.WriteLine(" Exception: {0}", v.GetType().Name);
- }
- Console.WriteLine();
- }
- // Display status of all tasks.
- foreach (var task in tasks)
- Console.WriteLine("Task {0} status is now {1}", task.Id, task.Status);
- Console.ReadKey();
- }
- static void DoSomeWork(int taskNum, CancellationToken ct)
- {
- // Was cancellation already requested?
- if (ct.IsCancellationRequested == true)
- {
- Console.WriteLine("Task {0} was cancelled before it got started.", taskNum);
- ct.ThrowIfCancellationRequested();
- }
- int maxIterations = 100;
- // NOTE!!! A "TaskCanceledException was unhandled
- // by user code" error will be raised here if "Just My Code"
- // is enabled on your computer. On Express editions JMC is
- // enabled and cannot be disabled. The exception is benign.
- // Just press F5 to continue executing your code.
- for (int i = 0; i <= maxIterations; i++)
- {
- // Do a bit of work. Not too much.
- var sw = new SpinWait();
- for (int j = 0; j <= 100; j++)
- sw.SpinOnce();
- if (ct.IsCancellationRequested)
- {
- Console.WriteLine("Task {0} cancelled", taskNum);
- ct.ThrowIfCancellationRequested();
- }
- }
- }
- }
四: 并行迭代 Task.Parallel
API 会判定同时执行多少个线程效率最高。效率由一个爬山算法来决定。
一个parallel并行的例子
- using System;
- using System.Collections.Generic;
- using System.Threading.Tasks;
- using System.Text;
- namespace Thread_Task_Sample_Parallel
- {
- class Program
- {
- static void Main(string[] args)
- {
- int[] data = new int[100];
- int i = 0;
- for (i = 0; i < data.Length; i++)
- {
- data[i] = i;
- Console.Write("{0} ", data[i]);
- }
- Console.WriteLine(" \n ");
- Console.WriteLine("\nParallel running ... ... \n");
- Parallel.For(0, 100, (j)=>
- {
- System.Threading.Thread.Sleep(1000);
- data[j] = data[j] * 2;
- Console.Write("{0} ", data[j]);
- });
- Console.WriteLine("\n\nParallel end ... ... \n");
- Console.WriteLine(" \n ");
- for (i = 0; i < data.Length; i++)
- {
- Console.Write("{0} ", data[i]);
- }
- Console.WriteLine(" \n ");
- Console.ReadKey();
- }
- }
- }
测试结果: