基礎拾遺
基礎拾遺——特性詳解
基礎拾遺——webservice詳解
基礎拾遺——redis詳解
基礎拾遺——反射詳解
基礎拾遺——委託、事件詳解
基礎拾遺——接口詳解
基礎拾遺——泛型詳解
基礎拾遺—–依賴注入
基礎拾遺—–數據註解與驗證
基礎拾遺—–mongoDB操作
基礎拾遺—-RabbitMQ
基礎拾遺—CLR
基礎拾遺—-多線程
前言
我們知道c# 程序是自上而下的,但有的時候部分程序使用時間較長比如下載文檔什麼的。這是就可以用到線程。線程可以理解為是程序的執行路徑,每個線程都定義了一個獨特的控制流。如果您的應用程序涉及到複雜的和耗時的操作,那麼設置不同的線程執行路徑往往是有益的,每個線程執行特定的工作。
1.線程的生命周期
線程生命周期開始於 System.Threading.Thread 類的對象被創建時,結束於線程被終止或完成執行時。
下面列出了線程生命周期中的各種狀態:
未啟動狀態:當線程實例被創建但 Start 方法未被調用時的狀況。
就緒狀態:當線程準備好運行並等待 CPU 周期時的狀況。
不可運行狀態:下面的幾種情況下線程是不可運行的:
已經調用 Sleep 方法
已經調用 Wait 方法
通過 I/O 操作阻塞
死亡狀態:當線程已完成執行或已中止時的狀況。
2.多線程的優缺點
2.1.優點
- 可以使用線程將代碼同其他代碼隔離,提高應用程序的可靠性。
- 可以使用線程來簡化編碼。
- 可以使用線程來實現併發執行。
- 可以提高CPU的利用率
2.2.缺點
- 線程開的越多,內存佔用越大。
- 協調和管理代碼的難度加大,需要CPU時間跟蹤線程。
- 線程之間對共享資源的訪問會相互影響,必須解決競用共享資源的問題。
- 銷毀線程需要了解可能發生的問題並對那些問題進行處理。
3.線程的實現
3.1.異步委託
關於委託基礎拾遺——委託、事件詳解這有詳細介紹,我們都知道調用委託Delegate()或者Delegate?.Invoke()。進行執行,但是主線程的代碼是從上至下進行執行的,那麼我們想要委託方法進行一個新的線程只需BeginInvoke生成異步方法調用即可。
3.3.1.實現
public delegate void ThreadDelegate();
static void MethodDelegata()
{
Console.WriteLine("MethodDelegata");
}
static void Main(string[] args)
{
ThreadDelegate d = MethodDelegata;
//BeginInvoke 兩個參數一個是執行完后回調方法,一個是返回結果,如委託有參數載氣前方添加即可。
d.BeginInvoke(null,null);
Console.WriteLine("Main");
Console.ReadKey();
}
執行結果如下
3.1.1.獲取線程返回值
線程執行時有可能執行時間過長,如果我們要獲取線程的返回值,這是就需要不回線程的狀態和利用線程的回調方法。
public delegate int ThreadDelegate(int i);
static int MethodDelegata(int i)
{
Console.WriteLine("MethodDelegata" + i);
Thread.Sleep(1000);
return 100;
}
static void Main(string[] args)
{
ThreadDelegate d = MethodDelegata;
//BeginInvoke 兩個參數一個是執行完后回調方法,一個是返回結果
IAsyncResult ar = d?.BeginInvoke(1, null, null);//獲取線程執行狀態
Console.WriteLine("Main");
while (!ar.IsCompleted) {//線程是否已執行完成,未完成執行
Console.WriteLine(".");
Thread.Sleep(10);//減少線程監測頻率
}
int res = d.EndInvoke(ar);//獲取線程的返回值
Console.WriteLine(res);
Console.ReadKey();
}
結果如下
·
我們如果不用while 的方式去等待方法執行結束,可以 ar.AsyncWaitHandle.WaitOne(1000); 但我們預估執行時間如果小於實際執行時間的化,返回值就獲取不到了。
bool isEnd = ar.AsyncWaitHandle.WaitOne(1000);
if (isEnd)
{
int res = d.EndInvoke(ar);//獲取線程的返回值
Console.WriteLine(res);
}
View Code
- 利用 d?.BeginInvoke(1, callBack, object) 回調方法
public delegate int ThreadDelegate(int i);
static int MethodDelegata(int i)
{
Console.WriteLine("MethodDelegata" + i);
Thread.Sleep(1000);
return 100;
}
static void Main(string[] args)
{
ThreadDelegate d = MethodDelegata;
//BeginInvoke 兩個參數一個是執行完后回調方法,一個是返回結果
IAsyncResult ar = d?.BeginInvoke(1, CallBack, d);//獲取線程執行狀態
Console.WriteLine("Main");
Console.ReadKey();
}
/// <summary>
/// 結束回調方法
/// </summary>
/// <param name="ar"></param>
private static void CallBack(IAsyncResult ar)
{
var obj=ar.AsyncState as ThreadDelegate;
int res = obj.EndInvoke(ar);
Console.WriteLine("線程結束,結果為:"+res);
}
我們通過lamda表達式優化一下上面的代碼
static void Main(string[] args)
{
ThreadDelegate d = MethodDelegata;
//BeginInvoke 兩個參數一個是執行完后回調方法,一個是返回結果
//IAsyncResult ar = d?.BeginInvoke(1, CallBack, d);//獲取線程執行狀態
d?.BeginInvoke(1, ar => {
int res = d.EndInvoke(ar);
Console.WriteLine("線程結束,結果為:" + res);
}, null);
Console.WriteLine("Main");
Console.ReadKey();
}
3.2.Thread 類
3.2.1.不帶參數
static void MethodThread()
{
Console.WriteLine("MethodDelegata");//第二個參數最多執行時間
Thread.Sleep(1000);
}
static void Main(string[] args)
{
Thread t = new Thread(MethodThread);//創建了thread 對象單位啟動
//Thread t = new Thread(()=> { Console.WriteLine("MethodDelegata"); Thread.Sleep(1000);});//可直接用lamda表達式
t.Start();
Console.WriteLine("Main");
Console.ReadKey();
}
3.2.2.帶參數
Start(obj) 傳參:定義方法如果有參數必須object
static void MethodThread(object s) { Console.WriteLine("MethodDelegata"); Thread.Sleep(1000); } static void Main(string[] args) { //創建了thread 對象單位啟動
Thread t = new Thread(MethodThread); t.Start("wokao");//傳遞參數
Console.WriteLine("Main"); Console.ReadKey(); }
對象傳參:定義存放數據和線程方法的類
class Program
{
static void Main(string[] args)
{
//創建了thread 對象單位啟動
ClassThead c = new ClassThead("1");
Thread t = new Thread(c.MethodThread);
t.Start();//傳遞參數
Console.WriteLine("Main");
Console.ReadKey();
}
}
public class ClassThead
{
private string wr;
public ClassThead(string s)
{
this.wr = s;
}
public void MethodThread()
{
Console.WriteLine("MethodDelegata");
Thread.Sleep(1000);
}
}
3.2.3 前台線程和後台線程
- 前台線程:只要存在有一個前台線程在運行,應用程序就在運行。
- 後台線程:應用程序關閉時,如果後台線程沒有執行完,會被強制性的關閉
- 默認情況下,用Thread類創建的線程是前台線程,線程池中的線程總是後台線程。
- thread.IsBackground = true; 設置為後台程序
static void MethodThread()
{
Console.WriteLine("MethodDelegata");
Thread.Sleep(10000);
Console.ReadKey();
}
static void Main(string[] args)
{
Thread t = new Thread(MethodThread);
t.IsBackground = true;//當main執行結束后,不管t是否執行結束程序都關閉
t.Start();//傳遞參數
Console.WriteLine("Main");
}
thread.Abort() 終止線程的執行。調用這個方法,會拋出一個ThreadAbortException類型的異常。
thread.Join() 將當前線程睡眠,等待thread線程執行完,然後再繼續執行當前線程。
3.3.線程池threadPool
上面已經說了線程是為後台線程,在這多線程的操作推薦使用線程池線程而非新建線程。因為就算只是單純的新建一個線程,這個線程什麼事情也不做,都大約需要1M的內存空間來存儲執行上下文數據結構,並且線程的創建與回收也需要消耗資源,耗費時間。而線程池的優勢在於線程池中的線程是根據需要創建與銷毀,是最優的存在。但是這也有個問題,那就是線程池線程都是後台線程,主線程執行完畢后,不會等待後台線程而直接結束程序。
//如果帶參數必須為object
static void MethodThreadPool(object obj)
{
Console.WriteLine("MethodDelegata"+Thread.CurrentThread.ManagedThreadId);//當前線程id
Thread.Sleep(1000);
}
static void Main(string[] args)
{
ThreadPool.QueueUserWorkItem(MethodThreadPool);// 必須帶參數
ThreadPool.QueueUserWorkItem(MethodThreadPool);
ThreadPool.QueueUserWorkItem(MethodThreadPool);
ThreadPool.QueueUserWorkItem(MethodThreadPool);
ThreadPool.QueueUserWorkItem(MethodThreadPool);
Console.WriteLine("Main");
Console.ReadKey();
}
3.4. Task
- Task是架構在Thread之上的,也就是說任務最終還是要拋給線程去執行。
- Task跟Thread不是一對一的關係,比如開10個任務並不是說會開10個線程,這一點任務有點類似線程池,但是任務相比線程池有很小的開銷和精確的控制
- 可以將任務入隊到線程池中異步執行。
- 線程池入隊的任務無法取消
- 沒有回調方法,可以使用委託實現回調
3.4.1.任務的定義
方法1
var t1 = new Task(() => TaskMethod("Task 1"));
t1.Start();
Task.WaitAll(t1);//等待所有任務結束
方法2
Task.Run(() => TaskMethod("Task 2"));
方法3
Task.Factory.StartNew(() => TaskMethod("Task 3")); 直接異步的方法
//或者
var t3=Task.Factory.StartNew(() => TaskMethod("Task 3"));
Task.WaitAll(t3);//等待所有任務結束
案列
static void Main(string[] args)
{
var t1 = new Task(() => TaskMethod("Task 1"));
var t2 = new Task(() => TaskMethod("Task 2"));
t2.Start();
t1.Start();
Task.WaitAll(t1, t2);
Task.Run(() => TaskMethod("Task 3"));
Task.Factory.StartNew(() => TaskMethod("Task 4"));
//標記為長時間運行任務,則任務不會使用線程池,而在單獨的線程中運行。
Task.Factory.StartNew(() => TaskMethod("Task 5"), TaskCreationOptions.LongRunning);
Console.WriteLine("主線程執行業務處理.");
//創建任務
Task task = new Task(() =>
{
Console.WriteLine("使用System.Threading.Tasks.Task執行異步操作.");
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
}
});
//啟動任務,並安排到當前任務隊列線程中執行任務(System.Threading.Tasks.TaskScheduler)
task.Start();
Console.WriteLine("主線程執行其他處理");
task.Wait();
Thread.Sleep(TimeSpan.FromSeconds(1));
Console.ReadLine();
}
static void TaskMethod(string name)
{
Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}",
name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
}
View Code
3.4.2.async/await
async是contextual關鍵字,await是運算符關鍵字。
async/await 結構可分成三部分:
- 調用方法:該方法調用異步方法,然後在異步方法執行其任務的時候繼續執行;
- 異步方法:該方法異步執行工作,然後立刻返回到調用方法;
- await 表達式:用於異步方法內部,指出需要異步執行的任務。一個異步方法可以包含多個 await 表達式(不存在 await 表達式的話 IDE 會發出警告)。
class Program
{
async static void AsyncFunction()
{
await Task.Delay(1);
Console.WriteLine("使用System.Threading.Tasks.Task執行異步操作.");
for (int i = 0; i < 10; i++)
{
Console.WriteLine(string.Format("AsyncFunction:i={0}", i));
}
}
public static void Main()
{
Console.WriteLine("主線程執行業務處理.");
AsyncFunction();
Console.WriteLine("主線程執行其他處理");
for (int i = 0; i < 10; i++)
{
Console.WriteLine(string.Format("Main:i={0}", i));
}
Console.ReadLine();
}
}
4.線程爭用與死鎖
class Program
{
static void ChangeState(object obj)
{
ClassThead c = obj as ClassThead;
while (true)
{
c.MethodThread();
}
}
//如果帶參數必須為object
static void Main(string[] args)
{
ClassThead c = new ClassThead();
Thread t = new Thread(ChangeState);
t.Start(c);
Console.WriteLine("Main");
Console.ReadKey();
}
}
public class ClassThead
{
private int state = 6;
public void MethodThread()
{
state++;
if (state == 6)
{
Console.WriteLine("MethodDelegata");
Thread.Sleep(1000);
}
state = 6;
}
}
View Code
可以從上面的方法中看到執行結果為空,雖然他在執行但是state一直都是>6的。所以是不執行的。
但如果開啟兩個線程的結果是什麼呢?
是執行的因為多個線程有可能是在執行時另一個線程給他賦值了。所以我們就要給對象加鎖
static void ChangeState(object obj)
{
ClassThead c = obj as ClassThead;
while (true)
{
lock (c)
{
c.MethodThread();
}
}
}
注:但是有可能會出現線程爭用一直等待的情況,所以在編程過程設計好鎖的順序
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※超省錢租車方案
※教你寫出一流的銷售文案?
※網頁設計最專業,超強功能平台可客製化
※聚甘新