多線程的操作在程序中也是比較常見的,比如開啟一個(gè)線程執(zhí)行一些比較耗時(shí)的操作(IO操作),而主線程繼續(xù)執(zhí)行當(dāng)前操作,不會(huì)造成主線程阻塞。線程又分為前臺(tái)線程和后臺(tái)線程,區(qū)別是:整個(gè)程序必須要運(yùn)行完前臺(tái)線程才會(huì)退出,而后臺(tái)線程會(huì)在程序退出的時(shí)候結(jié)束掉。Thread默認(rèn)創(chuàng)建的是前臺(tái)線程,而ThreadPool和Task默認(rèn)創(chuàng)建的是后臺(tái)線程,Thread可以通過設(shè)置?IsBackground
屬性將線程設(shè)置為后臺(tái)線程。
static void Main(string[] args) { Thread thread = new Thread(new
ThreadStart(NoParameterMethod)); thread.Start(); Console.WriteLine("程序已經(jīng)執(zhí)行完成");
}static void NoParameterMethod() { Thread.Sleep(1000); Console.WriteLine("
NoParameterMethod"); } 前臺(tái)線程
效果:
static void Main(string[] args) { Thread thread = new Thread(new
ThreadStart(NoParameterMethod)) { IsBackground= true }; thread.Start();
Console.WriteLine("程序已經(jīng)執(zhí)行完成"); } static void NoParameterMethod() { Thread.Sleep(
1000); Console.WriteLine("NoParameterMethod"); } 后臺(tái)線程
效果:
下面來說一下幾種開啟多線程的方法:
1、Thread
1.1 開啟一個(gè)線程,執(zhí)行一個(gè)不帶參數(shù)的方法
static void Main(string[] args) { Thread thread = new Thread(new
ThreadStart(NoParameterMethod));
//注意Start開啟線程之后,當(dāng)前線程不是說一定會(huì)立馬執(zhí)行
//而是說當(dāng)前線程已經(jīng)準(zhǔn)備好被CPU調(diào)用,至于CPU什么時(shí)候調(diào)用是需要看情況而定 thread.Start(); Console.WriteLine("
程序已經(jīng)執(zhí)行完成"); } static void NoParameterMethod() {
//使當(dāng)前線程停止1s Thread.Sleep(1000); Console.WriteLine("NoParameterMethod"); }
1.2開啟一個(gè)線程,執(zhí)行帶參數(shù)的方法
static void Main(string[] args) { Thread thread = new Thread(new
ParameterizedThreadStart(ParameterMethod));//要傳入的參數(shù)在Start的時(shí)候傳入 thread.Start("
ParameterMethod"); Console.WriteLine("程序已經(jīng)執(zhí)行完成"); } //參數(shù)類型必須為Object類型,方法只能有一個(gè)參數(shù)
//如果想傳入多個(gè)參數(shù),可已將參數(shù)封裝進(jìn)入一個(gè)類中 static void ParameterMethod(Object x) { Thread.Sleep(
1000); Console.WriteLine(x); }
2、ThreadPool
使用ThreadPool開啟一個(gè)線程
//無參 Thread.CurrentThread.ManagedThreadId是當(dāng)前線程的唯一標(biāo)識(shí)符
ThreadPool.QueueUserWorkItem(new WaitCallback(obj =>
Console.WriteLine(Thread.CurrentThread.ManagedThreadId)));//有參
ThreadPool.QueueUserWorkItem(new WaitCallback(obj =>
Console.WriteLine(Thread.CurrentThread.ManagedThreadId)),"參數(shù)");
ThreadPool是Thread的一個(gè)升級(jí)版,ThreadPool是從線程池中獲取線程,如果線程池中又空閑的元素,則直接調(diào)用,如果沒有才會(huì)創(chuàng)建,而Thread則是會(huì)一直創(chuàng)建新的線程,要知道開啟一個(gè)線程就算什么事都不做也會(huì)消耗大約1m的內(nèi)存,是非常浪費(fèi)性能的,接下來我們寫一個(gè)例子來看一下二者的區(qū)別:
#region 使用Thread開啟100個(gè)線程 for (int i = 0; i < 100; i++) { (new Thread(new
ThreadStart(() =>
Console.WriteLine(Thread.CurrentThread.ManagedThreadId)))).Start(); }#endregion
運(yùn)行結(jié)果:
我們可以看到每一個(gè)主線程表示id都是不同的,也就是說使用Thread開啟線程每次都是新創(chuàng)建一個(gè)
#region 使用ThreadPool開啟100個(gè)線程 for (int i = 0; i < 100; i++) {
ThreadPool.QueueUserWorkItem(new WaitCallback(obj =>
Console.WriteLine(Thread.CurrentThread.ManagedThreadId))); }#endregion
運(yùn)行結(jié)果:
相信區(qū)別已經(jīng)很明顯了,這里我再說一下,線程池中一開始是沒有一個(gè)線程的,使用ThreadPool開啟一個(gè)線程之后,線程執(zhí)行完畢,會(huì)加入到線程池中,后續(xù)需要再次開啟線程的時(shí)候查看線程池中有沒有空閑的線程,有則調(diào)用,沒有則創(chuàng)建,如此循環(huán)
二者之間還有一個(gè)區(qū)別,就是ThreadPool可以操控線程的狀態(tài),比如等待線程完成,或者終止超時(shí)子線程操作
取消子線程操作
CancellationTokenSource cts = new CancellationTokenSource();
ThreadPool.QueueUserWorkItem(new WaitCallback(CanCancelMethod),cts.Token);
cts.Cancel();
Console.ReadKey();
static void CanCancelMethod(Object obj) { CancellationToken ct =
(CancellationToken)obj;if (ct.IsCancellationRequested) { Console.WriteLine("
該線程已取消"); } //就算ct.IsCancellationRequested為真,接下來的代碼還是會(huì)執(zhí)行 //因?yàn)樵摲椒ú]有ruturn
Thread.Sleep(1000); Console.WriteLine($"
子線程{Thread.CurrentThread.ManagedThreadId}結(jié)束"); }
感覺這個(gè)取消子線程的方法和設(shè)置一個(gè)全局變量,然后通過判斷和更改全局變量的值,設(shè)置線程是否取消的效果一樣
ThreadPool的其他操作感興趣的可以自己搜索學(xué)一下,因?yàn)榻K止線程什么操作都是比較麻煩的,關(guān)于ThreadPool就不再多說了
3、Task
Task和ThreadPool是一樣的,都是從線程池中取空閑的線程
?使用Task開啟一個(gè)線程
//方法1 使用Task的Run方法 Task.Run(()=> { Console.WriteLine($"
線程{Thread.CurrentThread.ManagedThreadId}已開啟"); }); //方法2
使用Task工廠類TaskFactory對(duì)象的StartNew方法 (new TaskFactory()).StartNew(() => {
Console.WriteLine($"線程{Thread.CurrentThread.ManagedThreadId}已開啟"); });
Run和StartNew方法都是返回一個(gè)Task類型的對(duì)象,代表當(dāng)前開啟的線程,如果方法有返回值
//如果方法有返回值 Task<int> t1 = Task.Run<int>(() => { return 1; }); //
通過t1.Result查看返回的結(jié)果 Console.WriteLine(t1.Result);
取消線程操作的話和ThreadPool取消線程操作一樣
//1s后自動(dòng)取消線程 CancellationTokenSource cts = new CancellationTokenSource(1000); //
為取消線程注冊(cè)回調(diào)函數(shù) cts.Token.Register(()=> { Console.WriteLine("線程已取消"); });
Task.Run(()=> { Console.WriteLine("開始執(zhí)行"); Thread.Sleep(2000); //判斷當(dāng)前線程是否已被取消 if
(cts.Token.IsCancellationRequested) { Console.WriteLine("方法已結(jié)束"); return; }
Console.WriteLine("線程繼續(xù)執(zhí)行"); },cts.Token);
等待所有線程執(zhí)行完畢
//存放所有線程 List<Task> lst = new List<Task>(); //開啟10個(gè)線程 for (int i = 0;i < 10;i++
) { lst.Add(Task.Run(()=> { Thread.Sleep(new Random().Next(1,3000));
Console.WriteLine($"線程{Thread.CurrentThread.ManagedThreadId}"); })); } //
等待所有線程執(zhí)行完畢 Task.WaitAll(lst.ToArray()); Console.WriteLine("所有線程執(zhí)行完畢");
等待任意一個(gè)先線程執(zhí)行完畢
//存放所有線程 List<Task> lst = new List<Task>(); //開啟10個(gè)線程 for (int i = 0;i < 10;i++
) { lst.Add(Task.Run(()=> { Thread.Sleep(new Random().Next(1,3000));
Console.WriteLine($"線程{Thread.CurrentThread.ManagedThreadId}"); })); } //
等待任意線程執(zhí)行完畢 Task.WaitAny(lst.ToArray()); Console.WriteLine("已有現(xiàn)成執(zhí)行完畢");
對(duì)于Thread、ThreadPool和Task,如果要用多線程的話,優(yōu)先使用Task,如果版本不支持Task,則考慮ThreadPool
4、Parallel
Parallel循環(huán)開啟多線程,并行任務(wù),對(duì)于多線程開啟任務(wù),開啟的順序都是不確定的
Parallel.Invoke()
Action[] action = new Action[] { ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), }; Parallel.Invoke(action);
相當(dāng)于
Action[] action = new Action[] { ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), }; for (int i = 0; i <
action.Length; i++) { Task.Run(action[i]); }
Invoke時(shí)也可以進(jìn)行一些配置,例如配置線程池中只能最多保持一個(gè)線程
Action[] action = new Action[] { ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), ()=>Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"), }; Parallel.Invoke(new
ParallelOptions() { MaxDegreeOfParallelism= 1 }, action);
運(yùn)行結(jié)果:
Parallel.For()
//將迭代的結(jié)果保存起來 ParallelLoopResult plr = Parallel.For(1, 10, (i) => {
Console.WriteLine($"線程:{Thread.CurrentThread.ManagedThreadId}"); });
Console.WriteLine(plr.IsCompleted);
相當(dāng)于
for (int i = 1; i < 10; i++) { Task.Run(() => { Console.WriteLine($"
線程:{Thread.CurrentThread.ManagedThreadId}"); }); }
相對(duì)于循環(huán)Task.Run()更加簡(jiǎn)潔
Parallel.ForEach()
方法和foreach類似,不過是采用的是異步方式遍歷,要想被Parallel.ForEach()必須實(shí)現(xiàn)IEnumerable接口
Parallel.ForEach<String>(new List<String>() { "a","b","c","d","e","f","g","h","
i" }, (str) => { Console.WriteLine(str); });
運(yùn)行結(jié)果:
停止循環(huán)的方法
//將迭代的結(jié)果保存起來 ParallelLoopResult plr = Parallel.For(1, 10, (i,state) => {
Console.WriteLine($"線程:{Thread.CurrentThread.ManagedThreadId}"); if (i==4) { //
結(jié)束 state.Break(); } }); Console.WriteLine(plr.IsCompleted);
5、Async、Await
async和await關(guān)鍵字用來實(shí)現(xiàn)異步編程,async用來修飾方法,await用來調(diào)用方法,await關(guān)鍵字必須出現(xiàn)在有async的方法中,await調(diào)用的方法可以不用async關(guān)鍵字修飾,但是返回值類型必須為Task<T>類型,下面來說一下用法:
static void Main(string[] args)
{ Demo1(); Console.ReadKey(); } static async void Demo1()
{ await Demo2(); } static async Task<int> Demo2()
{ return 1; }
await開啟異步和Task開啟異步還是有區(qū)別的
例如下面兩個(gè)例子
我們先用Task開啟異步編程
static void Main(string[] args) { Console.WriteLine("主線程開始"); TaskDemo1();
Console.WriteLine("主線程結(jié)束"); Console.ReadKey(); } static void TaskDemo1() {
Console.WriteLine("異步開始"); Task.Run<int>(() => { return TaskDemo2(); });
Console.WriteLine("異步結(jié)束"); } static int TaskDemo2() { Console.WriteLine("子線程開始"
); Thread.Sleep(1000); Console.WriteLine("子線程結(jié)束"); return 1; }
我們這是可以大膽的猜測(cè)一下顯示的順尋
大致應(yīng)該是:主線程開始==》異步開始==》(子線程開始|異步結(jié)束)=》(子線程開始|主線程結(jié)束)==》(子線程開始)=》子線程結(jié)束
運(yùn)行結(jié)果:
果然和我們猜想的差不多,大致順序沒有變,接下來我們用async和await關(guān)鍵字開啟異步
static void Main(string[] args) { Console.WriteLine("主線程開始"); AsyncDemo1();
Console.WriteLine("主線程結(jié)束"); Console.ReadKey(); } static async void AsyncDemo1()
{ Console.WriteLine("異步開始"); await AsyncDemo2(); Console.WriteLine("異步結(jié)束"); }
static async Task<int> AsyncDemo2() { Console.WriteLine("子線程開始"); //當(dāng)前子線程暫停1s
await Task.Delay(1000); Console.WriteLine("子線程結(jié)束"); return 0; }
?按理說順序也會(huì)是:主線程開始==》異步開始==》(子線程開始|異步結(jié)束)=》(子線程開始|主線程結(jié)束)==》(子線程開始)=》子線程結(jié)束
但事實(shí)是:
Task和async&await關(guān)鍵字的區(qū)別就此處
首先說一下梳理一下Task的執(zhí)行過程(畫圖畫的很粗糙,重點(diǎn)是流程)
然后我們?cè)賮砜匆幌耡sync和await的執(zhí)行過程
現(xiàn)在問題已經(jīng)很清晰了,就是當(dāng)主線程執(zhí)行到await?AsyncDemo2()時(shí),會(huì)像是碰到了return語句一樣,退出當(dāng)前方法(AsyncDemo1),將當(dāng)前方法(AsyncDemo1)的后續(xù)執(zhí)行語句交給子線程來執(zhí)行,子線程會(huì)在執(zhí)行完AsyncDemo2方法之后,返回過來執(zhí)行AsyncDemo1方法。
這一點(diǎn)就是await與Task異步編程的不同點(diǎn)
?
熱門工具 換一換
感谢您访问我们的网站,您可能还对以下资源感兴趣:
调教肉文小说-国产成本人片免费av-空姐av种子无码-在线观看免费午夜视频-综合久久精品激情-国产成人丝袜视频在线观看软件-大芭区三区四区无码-啊啊好爽啊啊插啊用力啊啊-wanch视频网-国产精品成人a免费观看