Thread、ThreadPool、Task、Parallel的基本用法、區別以及弊端


多線程的操作在程序中也是比較常見的,比如開啟一個線程執行一些比較耗時的操作(IO操作),而主線程繼續執行當前操作,不會造成主線程阻塞。線程又分為前台線程和后台線程,區別是:整個程序必須要運行完前台線程才會退出,而后台線程會在程序退出的時候結束掉。Thread默認創建的是前台線程,而ThreadPool和Task默認創建的是后台線程,Thread可以通過設置 IsBackground 屬性將線程設置為后台線程。

static void Main(string[] args)
{
    Thread thread = new Thread(new ThreadStart(NoParameterMethod));
    thread.Start();
    Console.WriteLine("程序已經執行完成");
}

static void NoParameterMethod()
{
    Thread.Sleep(1000);
    Console.WriteLine("NoParameterMethod");
}
前台線程

效果:

static void Main(string[] args)
{
    Thread thread = new Thread(new ThreadStart(NoParameterMethod))
    {
        IsBackground = true
    };
    thread.Start();
    Console.WriteLine("程序已經執行完成");
}

static void NoParameterMethod()
{
    Thread.Sleep(1000);
    Console.WriteLine("NoParameterMethod");
}
后台線程

效果:

下面來說一下幾種開啟多線程的方法:

1、Thread

1.1 開啟一個線程,執行一個不帶參數的方法

static void Main(string[] args)
{
    Thread thread = new Thread(new ThreadStart(NoParameterMethod));
//注意Start開啟線程之后,當前線程不是說一定會立馬執行
//而是說當前線程已經准備好被CPU調用,至於CPU什么時候調用是需要看情況而定 thread.Start(); Console.WriteLine(
"程序已經執行完成"); } static void NoParameterMethod() {
//使當前線程停止1s
Thread.Sleep(1000); Console.WriteLine("NoParameterMethod"); }

1.2開啟一個線程,執行帶參數的方法

static void Main(string[] args)
{
    Thread thread = new Thread(new ParameterizedThreadStart(ParameterMethod));
    //要傳入的參數在Start的時候傳入
    thread.Start("ParameterMethod");
    Console.WriteLine("程序已經執行完成");
}
//參數類型必須為Object類型,方法只能有一個參數
//如果想傳入多個參數,可已將參數封裝進入一個類中
static void ParameterMethod(Object x) {
    Thread.Sleep(1000);
    Console.WriteLine(x);
}

2、ThreadPool

使用ThreadPool開啟一個線程

//無參    Thread.CurrentThread.ManagedThreadId是當前線程的唯一標識符
ThreadPool.QueueUserWorkItem(new WaitCallback(obj => Console.WriteLine(Thread.CurrentThread.ManagedThreadId)));
//有參
ThreadPool.QueueUserWorkItem(new WaitCallback(obj => Console.WriteLine(Thread.CurrentThread.ManagedThreadId)), "參數");

ThreadPool是Thread的一個升級版,ThreadPool是從線程池中獲取線程,如果線程池中又空閑的元素,則直接調用,如果沒有才會創建,而Thread則是會一直創建新的線程,要知道開啟一個線程就算什么事都不做也會消耗大約1m的內存,是非常浪費性能的,接下來我們寫一個例子來看一下二者的區別:

#region 使用Thread開啟100個線程
for (int i = 0; i < 100; i++)
{
    (new Thread(new ThreadStart(() => Console.WriteLine(Thread.CurrentThread.ManagedThreadId)))).Start();
}
#endregion

運行結果:

我們可以看到每一個主線程表示id都是不同的,也就是說使用Thread開啟線程每次都是新創建一個

#region 使用ThreadPool開啟100個線程
for (int i = 0; i < 100; i++)
{
    ThreadPool.QueueUserWorkItem(new WaitCallback(obj => Console.WriteLine(Thread.CurrentThread.ManagedThreadId)));
}
#endregion

運行結果:

相信區別已經很明顯了,這里我再說一下,線程池中一開始是沒有一個線程的,使用ThreadPool開啟一個線程之后,線程執行完畢,會加入到線程池中,后續需要再次開啟線程的時候查看線程池中有沒有空閑的線程,有則調用,沒有則創建,如此循環

二者之間還有一個區別,就是ThreadPool可以操控線程的狀態,比如等待線程完成,或者終止超時子線程操作

取消子線程操作

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為真,接下來的代碼還是會執行
    //因為該方法並沒有ruturn
    Thread.Sleep(1000);
    Console.WriteLine($"子線程{Thread.CurrentThread.ManagedThreadId}結束");
}

感覺這個取消子線程的方法和設置一個全局變量,然后通過判斷和更改全局變量的值,設置線程是否取消的效果一樣

ThreadPool的其他操作感興趣的可以自己搜索學一下,因為終止線程什么操作都是比較麻煩的,關於ThreadPool就不再多說了

3、Task

Task和ThreadPool是一樣的,都是從線程池中取空閑的線程

 使用Task開啟一個線程

//方法1  使用Task的Run方法
Task.Run(()=> {
    Console.WriteLine($"線程{Thread.CurrentThread.ManagedThreadId}已開啟");
});
//方法2   使用Task工廠類TaskFactory對象的StartNew方法
(new TaskFactory()).StartNew(() =>
{
    Console.WriteLine($"線程{Thread.CurrentThread.ManagedThreadId}已開啟");
});

Run和StartNew方法都是返回一個Task類型的對象,代表當前開啟的線程,如果方法有返回值

//如果方法有返回值
Task<int> t1 = Task.Run<int>(() => {
    return 1;
});
//通過t1.Result查看返回的結果
Console.WriteLine(t1.Result);

取消線程操作的話和ThreadPool取消線程操作一樣

//1s后自動取消線程
CancellationTokenSource cts = new CancellationTokenSource(1000);
//為取消線程注冊回調函數
cts.Token.Register(()=> {
    Console.WriteLine("線程已取消");
});

Task.Run(()=> {
    Console.WriteLine("開始執行");
    Thread.Sleep(2000);
    //判斷當前線程是否已被取消
    if (cts.Token.IsCancellationRequested) {
        Console.WriteLine("方法已結束");
        return;
    }
    Console.WriteLine("線程繼續執行");
},cts.Token);

等待所有線程執行完畢

//存放所有線程
List<Task> lst = new List<Task>();
//開啟10個線程
for (int i = 0;i < 10;i++) {
    lst.Add(Task.Run(()=> {
        Thread.Sleep(new Random().Next(1,3000));
        Console.WriteLine($"線程{Thread.CurrentThread.ManagedThreadId}");
    }));
}
//等待所有線程執行完畢
Task.WaitAll(lst.ToArray());
Console.WriteLine("所有線程執行完畢");

等待任意一個先線程執行完畢

//存放所有線程
List<Task> lst = new List<Task>();
//開啟10個線程
for (int i = 0;i < 10;i++) {
    lst.Add(Task.Run(()=> {
        Thread.Sleep(new Random().Next(1,3000));
        Console.WriteLine($"線程{Thread.CurrentThread.ManagedThreadId}");
    }));
}
//等待任意線程執行完畢
Task.WaitAny(lst.ToArray());
Console.WriteLine("已有現成執行完畢");

對於Thread、ThreadPool和Task,如果要用多線程的話,優先使用Task,如果版本不支持Task,則考慮ThreadPool

4、Parallel

Parallel循環開啟多線程,並行任務,對於多線程開啟任務,開啟的順序都是不確定的

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);

相當於

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時也可以進行一些配置,例如配置線程池中只能最多保持一個線程

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);

運行結果:

Parallel.For()

//將迭代的結果保存起來
ParallelLoopResult plr =  Parallel.For(1, 10, (i) =>
{
    Console.WriteLine($"線程:{Thread.CurrentThread.ManagedThreadId}");
});
Console.WriteLine(plr.IsCompleted);

相當於

for (int i = 1; i < 10; i++)
{
    Task.Run(() =>
    {
        Console.WriteLine($"線程:{Thread.CurrentThread.ManagedThreadId}");
    });
}

相對於循環Task.Run()更加簡潔

Parallel.ForEach()

方法和foreach類似,不過是采用的是異步方式遍歷,要想被Parallel.ForEach()必須實現IEnumerable接口

Parallel.ForEach<String>(new List<String>() {
    "a","b","c","d","e","f","g","h","i"
}, (str) =>
{
    Console.WriteLine(str);
});

運行結果:

停止循環的方法

//將迭代的結果保存起來
ParallelLoopResult plr =  Parallel.For(1, 10, (i,state) =>
{
    Console.WriteLine($"線程:{Thread.CurrentThread.ManagedThreadId}");
    if (i==4) {
        //結束
        state.Break();
    }
});
Console.WriteLine(plr.IsCompleted);

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM