一.Thread
1.Thread 是framework1.0时候就存在的,可以用TreadStart来启动多线程。
Stopwatch watch = new Stopwatch();//计时器
watch.Start();
Console.WriteLine($"*******btnThreads_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} *********");
ThreadStart threadStart = new ThreadStart(()=>this.DoSomethingLong("btnThreads_Click"));
Thread thread = new Thread(threadStart);
thread.Start();
watch.Stop();
Console.WriteLine($"*****btnThreads_Click End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}*****");
private void DoSomethingLong(string name)
{
Console.WriteLine($"****************DoSomethingLong Start {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
long lResult = 0;
for (int i = 0; i < 1000000000; i++)
{
lResult += i;
}
Console.WriteLine($"****************DoSomethingLong End {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
}
执行结果是:
这里插一句委托用lambda表达式的写法:
//这里为什么要写成 Action<string>,因为this.DoSomethingLong方法是有string参数的,Action要和它配套使用,Action的参数是in,表示传入类型,所以写成Action<string>
Action<string> action = this.DoSomethingLong;
还有这一句:
// ThreadStart是无参无返回值的,但是this.DoSomethingLong是需要传一个string参数,这里就有冲突了,要包一层
//包成()=>this.DoSomethingLong("btnThreads_Click"),就成了无参无返回值的委托
ThreadStart threadStart = new ThreadStart(()=>this.DoSomethingLong("btnThreads_Click"));
Thread还有一些其他的方法,比如:
thread.Suspend();//已经抛弃
thread.Resume();//已经抛弃
thread.Join();//做等待,现在用Thread时,也就这个方法靠谱了
thread.Abort();//已经抛弃
但是除去Join()方法,其他方法不靠谱,因为线程是操作系统分配的,也归操作系统管理,这些方法不一定真正被执行了。
thread.Join()的效果:
介绍一个概念,前台线程和后台线程。
thread.Start();//默认是前台线程,UI线程退出后,还会继续执行完;后台线程就直接退出了,只有Thread有前台线程,其他Task、 Parallel都没有。加上thread.IsBackground = true,变成后台线程。
2.既然Thread是framwork1.0时代的,那时候还没有调用回调函数callback方法,所以需要自己写。
private void ThreadWithCallback(ThreadStart threadStart,Action callback)
{
ThreadStart startNew = new ThreadStart(
() =>
{
threadStart.Invoke();
callback.Invoke();
});
Thread thread = new Thread(startNew);
thread.Start();
}
这样写,等于是threadStart.Invoke()执行完以后,再执行callback.Invoke(),它们在同一线程内是顺序执行的,达到了回调目的。
而测试代码可以这样写:
Stopwatch watch = new Stopwatch();
watch.Start();
Console.WriteLine($"****************btnThreads_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
this.ThreadWithCallback(()=>
{
Thread.Sleep(2000);
Console.WriteLine($"这里是ThreadStart {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
,()=>
{
Thread.Sleep(2000);
Console.WriteLine($"这里是callback {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
);
watch.Stop();
Console.WriteLine($"****************btnThreads_Click End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 耗时{watch.ElapsedMilliseconds}ms***************");
因为ThreadWithCallback方法的两个参数:ThreadStart 和 Action都是委托,所以可以这样写。
执行结果:
同样Thread是framwork1.0时代的,那时候还没有得到返回值的EndInvoke方法,所以需要自己写。
private Func<T> ThreadWithReturn<T>(Func<T> funcT)
{
T t = default(T);
//只有将funcT包到子线程里面,才不会卡界面
ThreadStart startNew = new ThreadStart(
()=>
{
t = funcT.Invoke();
});
Thread thread = new Thread(startNew);
thread.Start();
return new Func<T>(()=>
{
//这里既然要拿到返回值,就要等待线程计算完,所以要Join
thread.Join();
return t;
});
}
测试代码:
Stopwatch watch = new Stopwatch();
watch.Start();
Console.WriteLine($"****************btnThreads_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
Func<int> func = this.ThreadWithReturn(() =>//begininvoke
{
Thread.Sleep(2000);
Console.WriteLine($"这里是ThreadStart {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
return 12345;
});
Console.WriteLine("已经执行到这里了。。。");
int iResult = func.Invoke();//endinvoke
watch.Stop();
Console.WriteLine($"****************btnThreads_Click End iResult={iResult}, {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 耗时{watch.ElapsedMilliseconds}ms***************");
执行结果:
二.ThreadPool
ThreadPool是在framework2.0出现的,它避免了Thread给用户的权利太强大,做了限制,只有一个QueueUserWorkItem方法来启动线程。例如:
ThreadPool.QueueUserWorkItem(o =>
{
Thread.Sleep(5000);
this.DoSomethingLong("btnThreads_Click1");
});
ThreadPool的好处是:
1 去掉各种 api 避免滥用,降低复杂度
2 池化:1)减少创建/销毁的成本 2 )限制最大线程数量
什么是池化?池化就是线程池一开始就向操作系统申请了一些进程,准备好了放在线程池中。当程序需要进程时,向线程池要,用完之后,自己也不用销毁,直接送还给线程池,线程池的操作是framework底层做的,不需要人工干预,所以提供给用户调用的方法很少。
ManualResetEvent 它是一个信号值,可以像WaitOne搭配Thread一样,搭配ThreadPool使用。
ManualResetEvent mre = new ManualResetEvent(false);//false 关闭
new Action(() =>
{
Thread.Sleep(5000);
Console.WriteLine("委托的异步调用");
mre.Set();//打开
}).BeginInvoke(null, null);
//只有上面子线程执行完,mrc.Set()重新打开,才可以执行下面的方法
mre.WaitOne();
Console.WriteLine("12345");
mre.Reset();//关闭
new Action(() =>
{
Thread.Sleep(5000);
Console.WriteLine("委托的异步调用2");
mre.Set();//打开
}).BeginInvoke(null, null);
mre.WaitOne();
Console.WriteLine("23456");
但是,如果没有这个需求,就不要等待,阻塞线程,很容易死锁。最好用回调。
三.Task
目前最推荐的就是Task,它是framework3.0时候出来的,API的功能很强大。线程都来自线程池,所以都是后台线程。
1.WaitAll和WaitAny:这两者是等待。
WaitAll:主线程等待所有子线程执行完才返回,所以会卡界面
WaitAny:某个子线程执行完就返回。比如要获取数据,可以从接口,可以从数据库,可以从文件,那么,只要从一个途径拿到了数据,就返回,其他途径的结果可以不要了,类似于这种只需要一个结果的场景,就可以用WaitAny。
TaskFactory taskFactory = Task.Factory;// new TaskFactory();
都说List<Task> taskList = new List<Task>();
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_002")));
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_001")));
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_003")));
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_004")));
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_005")));
Task.WaitAny(taskList.ToArray());//卡界面
Console.WriteLine("某个任务都完成,才会执行");
Task.WaitAll(taskList.ToArray());//卡界面
Console.WriteLine("全部任务都完成,才会执行");
WaitAll和WaitAny,都是卡界面的,有没有不卡界面的呢?
2.ContinueWhenAll 和 ContinueWhenAny :这两者是回调。
一个是等待子线程全部完成,一个是等待某一个子线程完成,然后再开一个新的子线程执行下面的操作,它们都是回调。
TaskFactory taskFactory = Task.Factory;// new TaskFactory();
List<Task> taskList = new List<Task>();
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_002")));
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_001")));
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_003")));
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_004")));
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_005")));
//回调
taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"ContinueWhenAny {Thread.CurrentThread.ManagedThreadId.ToString("00")}"));
taskFactory.ContinueWhenAll(taskList.ToArray(), tList => Console.WriteLine($"这里是ContinueWhenAll {Thread.CurrentThread.ManagedThreadId.ToString("00")}"));
总结一下,WaitAll,WaitAny,ContinueWhenAll , ContinueWhenAny,有了这2个等待和2个回调的方法,可以解决绝大部分多线程执行的控制顺序的问题。
还有带返回值的(Func):
Task<int> intTask = taskFactory.StartNew(() => 123);
int iResult = intTask.Result;
还有单独一个多线程执行后的回调:
// "煎饼果子"就是t.AsyncState
Task task = taskFactory.StartNew(t => this.DoSomethingLong("btnTask_Click_005"), "煎饼果子")
.ContinueWith(t => Console.WriteLine($"这里是{t.AsyncState}的回调"));
千万不要在Task里面去启动Task