Threads异步和多线程-语言进阶2
- 一、Thread
- 1. 线程启动
- 2. 线程等待
- 3.前台线程/后台线程
- 4.扩展thread封装回调
- 二、Threadpool
- 1.线程池
- 2.线程池使用
- 3.ManualResetEvent 线程池等待
- 三、Task
- 1. Task启动方式
- 2.waitall 、waitany
- 1.waitall
- 2.waitany
- 3.WaitAll、waitany场景
- 4. 应用:线程数量控制
- 5.扩展:获取哪个任务先完成
- 3. WhenAny、WhenAll
- 4.ContinueWherAny、ContinueWhenAll
- 5.Delay() 延迟
- 四、并行运算Parallel
- 1. Parallel启动方式
- 2. Parallel限制线程数量
- 1.控制并发量
一、Thread
1. 线程启动
thread.Start();
ThreadStart threadStart = () => this.DoSomethingLong("btnThreads_Click");
Thread thread = new Thread(threadStart);
thread.Start() ;
2. 线程等待
- 当线程启动之后,我们需要线程等待时,可以用
thread.Join();//一直等待任务完成
thread.Join(500); //设置最多等待500ms - Sleep方式
while (thread.ThreadState != ThreadState.Stopped)
{
Thread.Sleep(100);//当前线程 休息100ms
}
3.前台线程/后台线程
- thread.IsBackground默认前台线程,启动之后一定会完成任务,阻止进程退出。
- thread.IsBackground=true;设置后台线程,随着进程退出
- Thread.Priority;//设置线程优先级,其中CPU会优先执行Highest,不代表说Highest最先完成。
4.扩展thread封装回调
- thread封装回调
//启动子线程计算--完成委托后,该线程去执行后续回调委托
private void ThreadWithCallback(Action act, Action callback)
{
Thread thread = new Thread(() =>
{
act.Invoke();
callback.Invoke():
});
thread.Start():
}
- 带返回的异步调用 需要获取返回值
private Func<T ThreadWithReturn<I>(Func<T> func)
{
T t = default(T);
Thread thread = new Thread(()=>
{
t = func.Invoke();
});
thread.Start() ;
//返回Func<T>
return ()=>
{
while (thread.ThreadState != ThreadState.Stopped)
{
Thread.Sleep(200);
}
}
return t;
}
其中while判断处也可以换成 thread.Join();
调用方式
二、Threadpool
1.线程池
- 线程池:线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统 的稳定性,使用线程池可以进行统一的分配,调优和监控。
2.线程池使用
- 线程池启用
ThreadPool.QueueUserWorkItem();//接受一个WaitCallback类型的参数;
WaitCallback:没返回值的委托;
- 线程限制:最大线程
ThreadPool.GetMaxThreads(out int workerThreads, out int completionPortlhreads();
GetMaxThreads:检索可以同时处于活动状态的线程池请求的数目。 所有大于此数目的请求将保持排队状态
workerThreads:线程池重辅助线程的最大数目。
completionPortlhreads:线程池中异步I/O线程的最大数目
3. 线程限制:最小线程
ThreadPool.GetMinThreads(out int workerThreads, out int completionPortlhreads();
3.ManualResetEvent 线程池等待
//ManualResetEvent 类 包含了一个bool属性
//false–WaitOne等待–Set–true–WaitOne直接过去
//true–WaitOne直接过去–reset–false–WaitOne等待
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(t =>
{
this.DoSomethingLong("btnThreadPool Click");
manualResetEvent.Set() :
//manualResetEvent.Reset()
});
manualResetEvent.WaitOne ();
Console.WriteLine("等着QueueUserWorkItem完成后才执行");
但不建议这么用,因为一般来说,不要阻塞线程池的线程。
三、Task
Task是基于ThreadPool,增加了多个API
1. Task启动方式
- Run
Task.Run(()=>this.DoSomethingLong("btnTask_Click1"));
- TaskFactory
TaskFactory taskFactory = Task.Factory;
taskFactory.StartNew(() => this.DoSomethingLong("btnTask Click3")):
new Task(0 => this.DoSomethingLong("btnTask Click4")).Start():
2.waitall 、waitany
1.waitall
waitall:阻塞当前线程,等着任务全部完成,才进入下一行,不过会卡界面。
当我们在多线程中,需要在所有任务都完成后,给与正确提示,如下例子,当所有开发任务完成,告诉甲方验收
可以看下这里的运行结果,实际开发任务并没有完成,但却告诉已经完成,不符合需求
这时我们就可以使用waitall阻塞当前线程,等完成后提示。
也可以传入指定时间,限时等待。
Task.WaitAll(taskList.ToArray(),1000);//限时等待
2.waitany
waitany:会阻塞当前线程,等着某个任务完成后,才进入下一行 卡界面
Task.WaitAny(taskList.ToArray());
Task.WaitAny(taskList.ToArray(),1000);
如果想做到 waitAll和waitAny不卡界面,则可以在包一层task,因为其中卡的是运行线程,包一层则意味着又是一个新的线程在执行
3.WaitAll、waitany场景
- WaitAll:一个业务查询操作有多个数据源——首页——多线程并发——拿到全部数据后才能返回
- WaitAny:一个商品搜索操作有多个数据源,商品搜索——多个数据源——多线程并发——只需要一个结果即可
4. 应用:线程数量控制
List<int> list = new List<int>();
for (int i = 0;i < 10000; i++){
list.Add(i);
}
//完成1000个任务,但只需要11个线程
Actlon<int>action = i =>
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString("00"));
Thread.Sleep(new Random(i).Next(100,300));
};
List<Task> taskList = new List<Task>();
foreach (var i in list)
{
int k = i;
taskList.Add(Task.Run(()=> action.Invoke(k)));
if (taskList.Count > 10)
{
//等待某个任务完成
Task.WaitAny(taskList.ToArray());
//未完成则保留
taskList = taskList.Where(t => t. Status != TaskStatus.RanToCompletion).Tolist());
}
}
Task.WhenA11(taskList.ToArray());
5.扩展:获取哪个任务先完成
- 使用TaskFactory在任务中追加标识,使用AsyncState获取任务
- 如果是Task的话 可以做个子类做属性标识
3. WhenAny、WhenAll
上述我们发现WaitAll、waitany一般情况会卡界面,那么接下来我们来解决这个情况
WhenAny、WhenAll:创建在task完成时,异步执行的延续任务,则不会卡界面,如下
4.ContinueWherAny、ContinueWhenAll
ContinueWherAny、ContinueWhenAll则是TaskFactory中使用,效果和WhenAny、WhenAll一样
5.Delay() 延迟
Task.Delay(1000);延迟 不会卡
Thread.Sleep(1000);等待,会卡
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start0():
Task.Delay(2000).ContinueWith(t =>
{
stopwatch.Stop () ;
Console.WriteLine(stopwatch.ElapsedMilliseconds);
}
如果想sleep也不卡的话,仍是包一层task开启个新线程
Stopwateh stopwatch = new Stopwatch();
stopwatch.Start();
Task.Run(()=>
{
Thread.sleep(2000)stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
}
四、并行运算Parallel
Parallel:并行编程,在Task的基础上做了封装,卡界面,因为主线程参与计算,节约了一个线程
1. Parallel启动方式
- Invole
Parallel.Invoke(() => this.Coding("爱书客","Client"),
()=> this.Coding("风动寂野","Portal"),
()=> this.Coding("笑看风云","Service"));
- For
//启动5个线程
Parallel.For(0,5,i =>Coding("爱书客","Client"+i));
- Foreach
Parallel.ForEach(new string[] { "0","1","2","3","4"),i => this.Coding("爱书客","client"+1));
2. Parallel限制线程数量
1.控制并发量
使用ParallelOptions 的MaxDegree0fParallelism 限制线程数量
ParallelOptions parallelOptions = new ParallelOptions();
parallel0ptions.MaxDegree0fParallelism = 3;//限制3个线程
Parallel.For(0,10,parallelOptions,i =>this.Coding("爱书客","Client"+ i));
从运行结果可以看到任意时刻只有三个线程在运行。