書接上回 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");
}
列印結果如下
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuMDMwkDZwMGO0kzM3ATOxEWO1UmM4UGZxQWMwgDZ4czNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.png)
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");
}