C#——多线程(重点Task)
基本概念:
线程运行的本质是函数的执行
多线程共享的资源是堆区的数据(非局部变量)
当所有前台线程都运行完毕后如果还有后台线程在运行,那么所有的后台线程都会被终止掉
线程优先级:CPU时间分配的倾向性,优先级高的多分配,但不表示执行顺序的必然性
临界资源访问:
- 锁
- 死锁,活锁和饥饿
死锁产生的条件:
死锁的解决:
- 忽略,罕见问题。解决代价大但收益很小的问题
- 预防,破坏四个条件
- 避免,银行家算法
- 检测恢复,检测到杀进程
实现方式:
1、Thread
- Start
- 最原始的多线程运作方式
- 支持object传入参数
- 同步化,返回结果需要自己实现
2、线程池
- QueueUserWorkItem
- ThreadPool上提供对于资源的管控。控制线程数量以及线程开辟和销毁的资源
- 支持object传入参数
3、委托
- BeginInvoke
- 支持明确类型的入参和出参
- 同步化,Callback和EndInvoke
- 死锁
4、Task
- 线程池管理
- 多种开启方式,TaskFactory、Task、StartNew
- 支持入参和出参(可以有返回值)
- 同步化,Continue、Wait、When
Continue 实例:
Task<int> t = new Task<int>(HeavyTaskOR, 5); //int 返回类型,需要Start //Task<int> t = new Task<int>(HeavyTaskOR, 5, TaskCreationOptions.LongRunning); //int 返回类型,需要Start t.Start(); //Continue相当于回调,就是在Task完成之后执行 t.ContinueWith(r => { this.Dispatcher.Invoke(() => { this.lb.Items.Add(r.Result); }); });
WhenAll 实例:
Task<int>[] ts = new Task<int>[5]; for (int i = 0; i < 5; i++) { int j = i; //解决闭包问题 ts[i] = Task.Run<int>(() => { Thread.Sleep(500); return HearyTaskIR(j); }); } this.lb.Items.Add("main"); Task.WhenAll<int>(ts).ContinueWith(rs => //WhenAll 等待线程全部完成后执行 { this.Dispatcher.Invoke(() => { this.lb.Items.Add(rs.Result[0] + 4); }); });
Wait 实例:
//队列Queue,线程安全 list = new ConcurrentQueue<string>(); var ts = new Task[5]; for(int i=0;i<5;i++) { int j = i; ts[i] = new Task(() => { Thread.Sleep(1000); list.Enqueue(j.ToString()); }); ts[i].Start(); } //Wait是阻塞线程,因为我们在UI线程上调用,所以界面会卡死,在后台线程不会,所以要避免在UI线程上调用此方法 Task.WaitAll(ts); foreach(string item in list.ToList()) { this.lb.Items.Add(item); }
Task 线程的取消,避免强杀线程
//线程的取消,避免强杀线程 //CancellationTokenSource是向应该取消的CancellationToken发送信号 CancellationTokenSource cts = new CancellationTokenSource(); Task<int> t = new Task<int>(() => HearyTaskORC(5,cts.Token)); t.Start(); cts.Cancel(); this.lb.Items.Add("main"); t.ContinueWith(r => { this.Dispatcher.Invoke(() => { this.lb.Items.Add(r.Result); }); });
父任务和子任务实例:
Task<int> tp = new Task<int>(() => { Task<int> tc = new Task<int>(() => { Thread.Sleep(1000); return 1; },TaskCreationOptions.AttachedToParent); //父线程的子线程,属于父线程的一部分 tc.Start(); tc.ContinueWith(r => { this.Dispatcher.Invoke(() => { this.lb.Items.Add(r.Result); }); }); return 2; }); tp.Start(); tp.ContinueWith(r => { this.Dispatcher.Invoke(() => { this.lb.Items.Add(r.Result); }); });
5、Thread和Task对比
- 线程池
- 出参(Task可以有返回参)
- 同步化
- 取消方式(Thread直接杀死线程,Task自己提供查询接口,判断是不是要结束线程)
6、Parallel
- For,Invoke
- Break
实例:
private void Parallel_Click(object sender, RoutedEventArgs e) { list = new ConcurrentQueue<string>(); //开10个线程 Parallel.For(0, 10, (i, state) => { //开到第6个时,不再继续开线程,但有时候可能已经开到了6个或7个,直到能响应此Break之后不再继续开 if (i > 5) { state.Break(); } list.Enqueue(i.ToString()); }); //Invoke可以传一系列的Action进去,此Action并行执行 Parallel.Invoke(() => { list.Enqueue("a"); }, () => { list.Enqueue("b"); }); foreach(string item in list.ToList()) { this.lb.Items.Add(item); } }
7、Async / Await(C#语法糖)
- 异步编程思维的转变
- Async函数返回类型只能为void和Task
- Async中应该要有Await,如果不写Await,编译器会将其变为同步执行函数
- Await只能等待Task
- UI问题
- ConfigureAwait指定是否恢复上下文
- Wait和Result是同步方法
实例:
private async void testAsync() { Task t = new Task(() => { Thread.Sleep(5000); this.Dispatcher.Invoke(() => { this.lb.Items.Add("task"); }); }); t.Start(); await t; //和Task.await区别在于不阻塞UI线程 this.lb.Items.Add("main"); /* //Result是将UI线程占用,等待结果,await运行完成后会回到UI,由于UI线程一直被占用,所以无法回到UI,await会一直等待,所以产生了死锁 // this.lb.Items.Add(LoadStringAsync().Result); string s = await LoadStringAsync(); this.lb.Items.Add(s); */ }
private async Task<string>LoadStringAsync() { Task<string> t1 = new Task<string>(() => { Thread.Sleep(3000); return "first"; }); t1.Start(); Task<string> t2 = new Task<string>(() => { Thread.Sleep(3000); return "second"; }); t2.Start(); string first = await t1; string second = await t2; return first + " " + second; }
8、Timer
- 定时任务,省去控制时间的循环
- DispatcherTimer可直接访问UI
- 被销毁问题
9、Dispatcher
- BeginInvoke(异步调用),Invoke(同步调用)
- UI主线程调用
- DispatcherPriority
发展进程:
- Thread
- ThreadPool
- Task
- Async/Await
祝大家早日成为技术大咖~