12 异步多线程(二)Thread,ThreadPool,Task

一.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

猜你喜欢

转载自blog.csdn.net/cainong2005/article/details/80574401