书接上回 Unity C#基础之 多线程的前世今生(上) 科普篇,下面为大家介绍从.NET 1.X到4.X版本中多线程的示例,推荐使用.NET 3.X版本
注意:打印提示中信息很重要,反馈出线程ID和顺序,便于理解
欢迎大家留言交流
- 【2018.06.20更新】Task.Delay(millisecond) .ContinueWith(t=>{DoSomething();}) //task延迟执行,不卡主线程
示例工程下载
.NET 1.X版本
- Thread的基本使用
- 基于Thread封装支持回调
- 基于Thread封装支持回调带返回值
/// <summary>
/// 多线程1.0 1.1
/// </summary>
private void ThreadsOnClick()
{
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
Debug.Log($"ThreadsOnClick Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
{
Action act = new Action(() => this.DoSomethingLong("ThreadsOnClick"));
ThreadStart threadStart = new ThreadStart(() =>
{
Thread.Sleep(5000);
this.DoSomethingLong("ThreadsOnClick");
});
Thread thread = new Thread(threadStart)
{
IsBackground = true//变成后台线程
};//Action和ThreadStart 约束一致,但是不同类型
thread.Start();//默认是前台线程,UI线程退出后,还会继续执行完;后台线程就直接退出了
//thread.Suspend();
//thread.Resume();
//thread.Join();//做等待
//thread.Abort();
//while (thread.ThreadState != System.Threading.ThreadState.Running)
//{
//}
}
watch.Stop();
Debug.Log($"ThreadsOnClick End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 耗时{watch.ElapsedMilliseconds}ms");
}
打印结果如下
private void ThreadsOnClick01()
{
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
Debug.Log($"ThreadsOnClick Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
this.ThreadWithCallback(() =>
{
Thread.Sleep(2000);
Debug.Log($"这里是ThreadWithCallback 线程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
, () =>
{
Thread.Sleep(2000);
Debug.Log($"这里是ThreadWithCallback回调 线程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
});
watch.Stop();
Debug.Log($"ThreadsOnClick End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 耗时{watch.ElapsedMilliseconds}ms");
}
/// <summary>
/// 基于Thread封装支持回调
/// BeginInvoke的回调
/// </summary>
/// <param name="threadStart"></param>
/// <param name="callback"></param>
private void ThreadWithCallback(ThreadStart threadStart, Action callback)
{
ThreadStart startNew = new ThreadStart(
() =>
{
threadStart();
callback.Invoke();
});
Thread thread = new Thread(startNew);
thread.Start();
}
private void ThreadsOnClick02()
{
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
Debug.Log($"ThreadsOnClick 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);
Debug.Log($"这里是ThreadStart {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
return 12345;
});
int iResult = func.Invoke();//相当于异步endinvoke
watch.Stop();
Debug.Log($"ThreadsOnClick End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 耗时{watch.ElapsedMilliseconds}ms");
}
/// <summary>
/// 基于Thread封装支持回调带返回值
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="funcT"></param>
/// <returns></returns>
private Func<T> ThreadWithReturn<T>(Func<T> funcT)
{
T t = default(T);
ThreadStart startNew = new ThreadStart(
() =>
{
t = funcT.Invoke();
});
Thread thread = new Thread(startNew);
thread.Start();
return new Func<T>(() =>
{
thread.Join();
return t;
});
}
.NET 2.X版本
- ThreadPool.QueueUserWorkItem
- ManualResetEvent
- ThreadPool.SetMaxThreads等参数设置
/// <summary>
/// 线程池 2.0 封装
/// 1 去掉各种 api 避免滥用,降低复杂度
/// 2 池化:1减少创建/销毁的成本 2 限制最大线程数量
/// </summary>
private void ThreadPoolOnClick()
{
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
Debug.Log($"ThreadPoolOnClick Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
{
ThreadPool.QueueUserWorkItem(
o =>
{
new Action(() =>
{
this.DoSomethingLong("Action:ThreadPoolOnClick");
//回调 就包一层,放在这里
}).Invoke();
Debug.Log("1234356786954");
});
ManualResetEvent mre = new ManualResetEvent(false); //相当于WaitOne() 、Join()一种等待状态,会阻碍主线程 状态变为True开始执行下一行Code
ThreadPool.QueueUserWorkItem(o =>
{
this.DoSomethingLong("ThreadPoolOnClick");
Debug.Log(o.ToString());
mre.Set();
}, "QueueUserWorkItem.State");
Debug.Log("before WaitOne");
mre.WaitOne();
Debug.Log("after WaitOne");
}
watch.Stop();
Debug.Log($"ThreadPoolOnClick End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 耗时{watch.ElapsedMilliseconds}ms");
}
private void ThreadPoolOnClick01()
{
#region ManualResetEvent
//没有需求。就别等待 阻塞线程
{
ManualResetEvent mre = new ManualResetEvent(false);//false 关闭
new Action(() =>
{
Thread.Sleep(1000);
Debug.Log($"委托的异步调用01 线程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 时间点: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
mre.Set();//打开
}).BeginInvoke(null, null);
mre.WaitOne();
Debug.Log(" mre.WaitOne()状态为打开");
mre.Reset();//关闭
new Action(() =>
{
Thread.Sleep(1000);
Debug.Log($"委托的异步调用02 线程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 时间点: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
mre.Set();//打开
}).BeginInvoke(null, null);
mre.WaitOne();
Debug.Log("结束");
}
#endregion
}
public void ThreadPoolSetParameter()
{
#region PoolSet
ThreadPool.SetMaxThreads(8, 8);//最小也是核数
ThreadPool.SetMinThreads(8, 8);
int workerThreads = 0;
int ioThreads = 0;
ThreadPool.GetMaxThreads(out workerThreads, out ioThreads);
Debug.Log(String.Format("Max worker threads: {0}; Max I/O threads: {1}", workerThreads, ioThreads));
ThreadPool.GetMinThreads(out workerThreads, out ioThreads);
Debug.Log(String.Format("Min worker threads: {0}; Min I/O threads: {1}", workerThreads, ioThreads));
ThreadPool.GetAvailableThreads(out workerThreads, out ioThreads);
Debug.Log(String.Format("Available worker threads: {0}; Available I/O threads: {1}", workerThreads, ioThreads));
#endregion
}
.NET 3.X版本 (推荐)
- Task.Run&&TaskFactory 和状态等待
- ContinueWith
- Parallel
/// <summary>
/// Task 3.0
/// 使用的是线程池的线程 全部是后台线程
/// API很强大
///
/// 多线程:业务是可以并发执行
///
/// 千万不要在Task里面去启动Task
/// </summary>
private void TaskOnClick()
{
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
Debug.Log($"****************TaskOnClick Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
Task t1 = Task.Run(new Action(() =>
{
this.DoSomethingLong("Task.Run:TaskOnClick");
}));
TaskFactory taskFactory = Task.Factory;//等同于 new TaskFactory();
List<Task> taskList = new List<Task>();
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_001")));
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_002")));
taskList.Add(taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_003")));
//回调
//taskList.Add(taskFactory.ContinueWhenAny(taskList.ToArray(), t => Debug.Log($"这里是ContinueWhenAny {Thread.CurrentThread.ManagedThreadId.ToString("00")}")));//ContinueWhenAny其中任何一个线程完成回调 异步检测完成不卡界面
//taskList.Add(taskFactory.ContinueWhenAll(taskList.ToArray(), tList => Debug.Log($"这里是ContinueWhenAll {Thread.CurrentThread.ManagedThreadId.ToString("00")}")));//ContinueWhenAll所有线程完成回调 异步检测完成不卡界面
taskFactory.ContinueWhenAny(taskList.ToArray(), t => Debug.Log($"这里是ContinueWhenAny {Thread.CurrentThread.ManagedThreadId.ToString("00")}"));//ContinueWhenAny其中任何一个线程完成回调 异步检测完成不卡界面
taskFactory.ContinueWhenAll(taskList.ToArray(), tList => Debug.Log($"这里是ContinueWhenAll {Thread.CurrentThread.ManagedThreadId.ToString("00")}"));//ContinueWhenAll所有线程完成回调 异步检测完成不卡界面
//需要多线程加快速度 同时又要求某个完成后,才能返回
//多业务操作 希望并发,,但是某个完成后,才能返回
//Task.WaitAny(taskList.ToArray());//卡界面
//Debug.Log("某个任务都完成,才会执行");
//需要多线程加快速度 同时又要求全部完成后,才能返回
//多业务操作 希望并发,,但是全部完成后,才能返回
//Task.WaitAll(taskList.ToArray());//卡界面
//Debug.Log("全部任务都完成,才会执行");
watch.Stop();
Debug.Log($"****************TaskOnClick End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 耗时{watch.ElapsedMilliseconds}ms***************");
}
private void TaskOnClick01()
{
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
Debug.Log($"****************TaskOnClick01 Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
TaskFactory taskFactory = Task.Factory;//等同于 new TaskFactory();
Task task = taskFactory.StartNew(t => this.DoSomethingLong("btnTask_Click_005"), "菜鸟海澜").ContinueWith(t => Debug.Log($"这里是{t.AsyncState}的回调"));
Task<int> intTask = taskFactory.StartNew(() => 123);
int iResult = intTask.Result;
Debug.Log($"线程结果{iResult}");
watch.Stop();
Debug.Log($"****************TaskOnClick01 End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 耗时{watch.ElapsedMilliseconds}ms***************");
}
.NET 4.X版本
- Parallel.Invoke
- Parallel.For
- Parallel.ForEach
- ParallelOptions&&ParallelLoopState
/// <summary>
/// Parallel
/// </summary>
private void ParallelOnClick()
{
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
Debug.Log($"ParallelOnClick Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
//跟task很像,==task+waitall 卡界面 启动多个线程计算 主线程也参与计算,就是节约了一个线程
Parallel.Invoke(() => this.DoSomethingLong("btnParallel_Click_001"),
() => this.DoSomethingLong("btnParallel_Click_002"),
() => this.DoSomethingLong("btnParallel_Click_003"),
() => this.DoSomethingLong("btnParallel_Click_004"),
() => this.DoSomethingLong("btnParallel_Click_005"));
watch.Stop();
Debug.Log($"ParallelOnClick End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 耗时{watch.ElapsedMilliseconds}ms");
}
打印结果如下,主线程参与计算
private void ParallelOnClick01()
{
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
Debug.Log($"ParallelOnClick01 Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
Parallel.For(0, 5, t =>
{
this.DoSomethingLong($"btnParallel_Click_00{t}");
});
watch.Stop();
Debug.Log($"ParallelOnClick01 End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 耗时{watch.ElapsedMilliseconds}ms");
}
private void ParallelOnClick02()
{
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
Debug.Log($"ParallelOnClick02 Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, t =>
{
this.DoSomethingLong($"btnParallel_Click_00{t}");
});
watch.Stop();
Debug.Log($"ParallelOnClick02 End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 耗时{watch.ElapsedMilliseconds}ms");
}
private void ParallelOnClick03()
{
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
Debug.Log($"ParallelOnClick03 Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
ParallelOptions options = new ParallelOptions()
{
MaxDegreeOfParallelism = 3//最大并行数量(线程数)
};
//Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, options, t =>
//{
// this.DoSomethingLong($"btnParallel_Click_00{t}");
//});
Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, options, (t, state) =>
{
this.DoSomethingLong($"btnParallel_Click_00{t}");
if (t == 4)
{
/*
* 调用Stop方法表明,尚未启动的循环的任何迭代都不需要运行。它有效地取消了循环的任何额外迭代。但是,它并没有停止任何已经开始执行的迭代。
*
*/
state.Stop();//没有运行的线程会取消结束
Debug.Log($"Stop全部线程 线程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
return;
}
if (state.IsStopped)
{
Debug.Log($"已经被其他线程调用Stop 线程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
return;
}
/*
* //break 与stop同时使用可能触发异常:InvalidOperationException: Stop was called after Break was called
* https://msdn.microsoft.com/en-us/library/system.threading.tasks.parallelloopstate.break(v=vs.100).aspx
* https://msdn.microsoft.com/en-us/library/system.threading.tasks.parallelloopstate.stop.aspx
*/
// if (t == 2)
//{
// Debug.Log($"Break线程 线程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
// state.Break();//停止当前的
// return;
//}
});
watch.Stop();
Debug.Log($"ParallelOnClick03 End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 耗时{watch.ElapsedMilliseconds}ms");
}