欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C# 線程相關(guān)知識(shí)總結(jié)

 更新時(shí)間:2020年06月17日 10:46:54   作者:JoeSnail  
這篇文章主要介紹了C# 線程相關(guān)知識(shí),文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下

初識(shí)線程

線程是一個(gè)獨(dú)立的運(yùn)行單元,每個(gè)進(jìn)程內(nèi)部都有多個(gè)線程,每個(gè)線程都可以各自同時(shí)執(zhí)行指令。每個(gè)線程都有自己獨(dú)立的棧,但是與進(jìn)程內(nèi)的其他線程共享內(nèi)存。但是對于.NET的客戶端程序(Console,WPF,WinForms)是由CLR創(chuàng)建的單線程(主線程,且只創(chuàng)建一個(gè)線程)來啟動(dòng)。在該線程上可以創(chuàng)建其他線程。

圖:

線程工作方式

多線程由內(nèi)部線程調(diào)度程序管理,線程調(diào)度器通常是CLR委派給操作系統(tǒng)的函數(shù)。線程調(diào)度程序確保所有活動(dòng)線程都被分配到合適的執(zhí)行時(shí)間,線程在等待或阻止時(shí) (例如,在一個(gè)獨(dú)占鎖或用戶輸入) 不會(huì)消耗 CPU 時(shí)間。
在單處理器計(jì)算機(jī)上,線程調(diào)度程序是執(zhí)行時(shí)間切片 — 迅速切換每個(gè)活動(dòng)線程。在 Windows 中, 一個(gè)時(shí)間片是通常數(shù)十毫秒為單位的區(qū)域 — — 相比來說 線程間相互切換比CPU更消耗資源。在多處理器計(jì)算機(jī)上,多線程用一種混合的時(shí)間切片和真正的并發(fā)性來實(shí)現(xiàn),不同的線程會(huì)在不同的cpu運(yùn)行代碼。

創(chuàng)建線程

如:

using System;
using System.Threading;

class ThreadTest
{
 static void Main()
 {
  Thread t = new Thread (Write2);     // 創(chuàng)建線程t
  t.Start();                // 執(zhí)行 Write2()
 
  // 同時(shí)執(zhí)行主線程上的該方法
  for (int i = 0; i < 1000; i++) Console.Write ("1");
 }
 
 static void Write2()
 {
  for (int i = 0; i < 1000; i++) Console.Write ("2");
 }
}

輸出

111122221122221212122221212......

在主線程上創(chuàng)建了一個(gè)新的線程,該新線程執(zhí)行WrWrite2方法,在調(diào)用t.Start()時(shí),主線程并行,輸出“1”。

圖:

線程Start()之后,線程的IsAlive屬性就為true,直到該線程結(jié)束(當(dāng)線程傳入的方法結(jié)束時(shí),該線程就結(jié)束)。

CLR使每個(gè)線程都有自己獨(dú)立的內(nèi)存棧,所以每個(gè)線程的本地變量都相互獨(dú)立。

如:

static void Main() 
{
 new Thread (Go).Start();   // 創(chuàng)建一個(gè)新線程,并調(diào)用Go方法
 Go();             // 在主線程上調(diào)用Go方法
}
 
static void Go()
{
 // 聲明一個(gè)本地局部變量 cycles
 for (int cycles = 0; cycles < 5; cycles++) Console.Write ('N');
}

輸出

NNNNNNNNNN (共輸出10個(gè)N)

在新線程和主線程上調(diào)用Go方法時(shí)分別創(chuàng)建了變量cycles,這時(shí)cycles在不同的線程棧上,所以相互獨(dú)立不受影響。

圖:

如果不同線程指向同一個(gè)實(shí)例的引用,那么不同的線程共享該實(shí)例。

如:

class ThreadTest
{
 //全局變量
 int i;
 
 static void Main()
 {
  ThreadTest tt = new ThreadTest();  // 創(chuàng)建一個(gè)ThreadTest類的實(shí)例
  new Thread (tt.Go).Start();
  tt.Go();
 }
 
 // Go方法屬于ThreadTest的實(shí)例
 void Go() 
 {
   if (i==1) { ++i; Console.WriteLine (i); }
 }
}

輸出

2

新線程和主線程上調(diào)用了同一個(gè)實(shí)例的Go方法,所以變量i共享。

靜態(tài)變量也可以被多線程共享

class ThreadTest 
{
 static int i;  // 靜態(tài)變量可以被線程共享
 
 static void Main()
 {
  new Thread (Go).Start();
  Go();
 }
 
 static void Go()
 {
  if (i==1) { ++i; Console.WriteLine (i); }
 }
}

輸出

2

如果將Go方法的代碼位置互換

 static void Go()
 {
  if (i==1) { Console.WriteLine (i);++i;}
 }

輸出

1 1(有時(shí)輸出一個(gè),有時(shí)輸出兩個(gè))

如果新線程在Write之后,done=true之前,主線程也執(zhí)行到了write那么就會(huì)有兩個(gè)done。

不同線程在讀寫共享字段時(shí)會(huì)出現(xiàn)不可控的輸出,這就是多線程的線程安全問題。

解決方法: 使用排它鎖來解決這個(gè)問題--lock

class ThreadSafe 
{
 static bool done;
 static readonly object locker = new object();
 
 static void Main()
 {
  new Thread (Go).Start();
  Go();
 }
 
 static void Go()
 {
  //使用lock,確保一次只有一個(gè)線程執(zhí)行該代碼
  lock (locker)
  {
   if (!done) { Console.WriteLine ("Done"); done = true; }
  }
 }
}

當(dāng)多個(gè)線程都在爭取這個(gè)排它鎖時(shí),一個(gè)線程獲取該鎖,其他線程會(huì)處于blocked狀態(tài)(該狀態(tài)時(shí)不消耗cpu),等待另一個(gè)線程釋放鎖時(shí),捕獲該鎖。這就保證了一次
只有一個(gè)線程執(zhí)行該代碼。

Join和Sleep

Join可以實(shí)現(xiàn)暫停另一個(gè)線程,直到調(diào)用Join方法的線程結(jié)束。

static void Main()
{
 Thread t = new Thread (Go);
 t.Start();
 t.Join();
 Console.WriteLine ("Thread t has ended!");
}
 
static void Go()
{
 for (int i = 0; i < 1000; i++) Console.Write ("y");
}

輸出

yyyyyy..... Thread t has ended!

線程t調(diào)用Join方法,阻塞主線程,直到t線程執(zhí)行結(jié)束,再執(zhí)行主線程。

Sleep:暫停該線程一段時(shí)間

Thread.Sleep (TimeSpan.FromHours (1)); // 暫停一個(gè)小時(shí)
Thread.Sleep (500);           // 暫停500毫秒

Join是暫停別的線程,Sleep是暫停自己線程。

上面的例子是使用Thread類的構(gòu)造函數(shù),給構(gòu)造函數(shù)傳入一個(gè)ThreadStart委托。來實(shí)現(xiàn)的。

public delegate void ThreadStart();

然后調(diào)用Start方法,來執(zhí)行該線程。委托執(zhí)行完該線程也結(jié)束。

如:

class ThreadTest
{
 static void Main() 
 {
  Thread t = new Thread (new ThreadStart (Go));
 
  t.Start();  // 執(zhí)行Go方法
  Go();    // 同時(shí)在主線程上執(zhí)行Go方法
 }
 
 static void Go()
 {
  Console.WriteLine ("hello!");
 }
}

多數(shù)情況下,可以不用new ThreadStart委托。直接在構(gòu)造函數(shù)里傳入void類型的方法。

Thread t = new Thread (Go); 

使用lambda表達(dá)式

static void Main()
{
 Thread t = new Thread ( () => Console.WriteLine ("Hello!") );
 t.Start();
}

Foreground線程和Background線程

默認(rèn)情況下創(chuàng)建的線程都是Foreground,只要有一個(gè)Foregournd線程在執(zhí)行,應(yīng)用程序就不會(huì)關(guān)閉。
Background線程則不是。一旦Foreground線程執(zhí)行完,應(yīng)用程序結(jié)束,background就會(huì)強(qiáng)制結(jié)束。
可以用IsBackground來查看該線程是什么類型的線程。

線程異常捕獲

public static void Main()
{
 try
 {
  new Thread (Go).Start();
 }
 catch (Exception ex)
 {
  // 不能捕獲異常
  Console.WriteLine ("Exception!");
 }
}
 
static void Go() { throw null; }  //拋出 Null異常

此時(shí)并不能在Main方法里捕獲線程Go方法的異常,如果是Thread自身的異常可以捕獲。

正確捕獲方式:

public static void Main()
{
  new Thread (Go).Start();
}
 
static void Go()
{
 try
 {
  // ...
  throw null;  // 這個(gè)異常會(huì)被下面捕獲
  // ...
 }
 catch (Exception ex)
 {
   // ...
 }
}

線程池

當(dāng)創(chuàng)建一個(gè)線程時(shí),就會(huì)消耗幾百毫秒cpu,創(chuàng)建一些新的私有局部變量棧。每個(gè)線程還消耗(默認(rèn))約1 MB的內(nèi)存。線程池通過共享和回收線程,允許在不影響性能的情況下啟用多線程。
每個(gè).NET程序都有一個(gè)線程池,線程池維護(hù)著一定數(shù)量的工作線程,這些線程等待著執(zhí)行分配下來的任務(wù)。

線程池線程注意點(diǎn):

1 線程池的線程不能設(shè)置名字(導(dǎo)致線程調(diào)試?yán)щy)。

2 線程池的線程都是background線程

3 阻塞一個(gè)線程池的線程,會(huì)導(dǎo)致延遲。

4 可以隨意設(shè)置線程池的優(yōu)先級,在回到線程池時(shí)改線程就會(huì)被重置。
通過Thread.CurrentThread.IsThreadPoolThread.可以查看該線程是否是線程池的線程。

使用線程池創(chuàng)建線程的方法:

  • Task
  • ThreadPool.QueueUserWorkItem
  • Asynchronous delegates
  • BackgroundWorker

TPL

Framework4.0下可以使用Task來創(chuàng)建線程池線程。調(diào)用Task.Factory.StartNew(),傳遞一個(gè)委托

  • Task.Factory.StartNew
  • static void Main() 
    {
     Task.Factory.StartNew (Go);
    }
     
    static void Go()
    {
     Console.WriteLine ("Hello from the thread pool!");
    }

Task.Factory.StartNew 返回一個(gè)Task對象??梢哉{(diào)用該Task對象的Wait來等待該線程結(jié)束,調(diào)用Wait時(shí)會(huì)阻塞調(diào)用者的線程。

  • Task構(gòu)造函數(shù)   給Task構(gòu)造函數(shù)傳遞Action委托,或?qū)?yīng)的方法,調(diào)用start方法,啟動(dòng)任務(wù)
  • static void Main() 
    {
     Task t=new Task(Go);
     t.Start();
    }
     
    static void Go()
    {
     Console.WriteLine ("Hello from the thread pool!");
    }
  • Task.Run    直接調(diào)用Task.Run傳入方法,執(zhí)行。
  • static void Main() 
    {
     Task.Run(() => Go());
    }
     
    static void Go()
    {
     Console.WriteLine ("Hello from the thread pool!");
    }

QueueUserWorkItem

QueueUserWorkItem沒有返回值。使用 QueueUserWorkItem,只需傳遞相應(yīng)委托的方法就行。

static void Main()
{
 //Go方法的參數(shù)data此時(shí)為空
 ThreadPool.QueueUserWorkItem (Go);
 //Go方法的參數(shù)data此時(shí)為123
 ThreadPool.QueueUserWorkItem (Go, 123);
 Console.ReadLine();
}
 
static void Go (object data) 
{
 Console.WriteLine ("Hello from the thread pool! " + data);
}

委托異步

委托異步可以返回任意類型個(gè)數(shù)的值。
使用委托異步的方式:

  1. 聲明一個(gè)和方法匹配的委托
  2. 調(diào)用該委托的BeginInvoke方法,獲取返回類型為IAsyncResult的值
  3. 調(diào)用EndInvoke方法傳遞IAsyncResulte類型的值獲取最終結(jié)果

如:

static void Main()
{
 Func<string, int> method = Work;
 IAsyncResult cookie = method.BeginInvoke ("test", null, null);
 //
 // ... 此時(shí)可以同步處理其他事情
 //
 int result = method.EndInvoke (cookie);
 Console.WriteLine ("String length is: " + result);
}
 
static int Work (string s) { return s.Length; }

使用回調(diào)函數(shù)來簡化委托的異步調(diào)用,回調(diào)函數(shù)參數(shù)為IAsyncResult類型

static void Main()
{
 Func<string, int> method = Work;
 method.BeginInvoke ("test", Done, method);
 // ...
 //并行其他事情
}
 
static int Work (string s) { return s.Length; }
 
static void Done (IAsyncResult cookie)
{
 var target = (Func<string, int>) cookie.AsyncState;
 int result = target.EndInvoke (cookie);
 Console.WriteLine ("String length is: " + result);
}

使用匿名方法

Func<string, int> f = s => { return s.Length; };
 f.BeginInvoke("hello", arg =>
 {
   var target = (Func<string, int>)arg.AsyncState;
   int result = target.EndInvoke(arg);
   Console.WriteLine("String length is: " + result);
 }, f);

線程傳參和線程返回值

Thread

Thread構(gòu)造函數(shù)傳遞方法有兩種方式:

public delegate void ThreadStart();
public delegate void ParameterizedThreadStart (object obj);

所以Thread可以傳遞零個(gè)或一個(gè)參數(shù),但是沒有返回值。

  • 使用lambda表達(dá)式直接傳入?yún)?shù)。
  • static void Main()
    {
     Thread t = new Thread ( () => Print ("Hello from t!") );
     t.Start();
    }
     
    static void Print (string message) 
    {
     Console.WriteLine (message);
    }
  • 調(diào)用Start方法時(shí)傳入?yún)?shù)
  • static void Main()
    {
     Thread t = new Thread (Print);
     t.Start ("Hello from t!");
    }
     
    static void Print (object messageObj)
    {
     string message = (string) messageObj;  
     Console.WriteLine (message);
    }

Lambda簡潔高效,但是在捕獲變量的時(shí)候要注意,捕獲的變量是否共享。

如:

for (int i = 0; i < 10; i++)
 new Thread (() => Console.Write (i)).Start();

輸出

0223447899

因?yàn)槊看窝h(huán)中的i都是同一個(gè)i,是共享變量,在輸出的過程中,i的值會(huì)發(fā)生變化。

解決方法-局部域變量

for (int i = 0; i < 10; i++)
{
 int temp = i;
 new Thread (() => Console.Write (temp)).Start();
}

這時(shí)每個(gè)線程都指向新的域變量temp(此時(shí)每個(gè)線程都有屬于自己的花括號的域變量)在該線程中temp不受其他線程影響。

委托

委托可以有任意個(gè)傳入和輸出參數(shù)。以Action,F(xiàn)unc來舉例。

  • Action 有零個(gè)或多個(gè)傳入?yún)?shù),但是沒有返回值。
  • Func 有零個(gè)或多個(gè)傳入?yún)?shù),和一個(gè)返回值。
  •  Func<string, int> method = Work;
     IAsyncResult cookie = method.BeginInvoke("test", null, null);
     //
     // ... 此時(shí)可以同步處理其他事情
     //
     int result = method.EndInvoke(cookie);
     Console.WriteLine("String length is: " + result);    
    
     int Work(string s) { return s.Length; }

使用回調(diào)函數(shù)獲取返回值

static void Main()
{
 Func<string, int> method = Work;
 method.BeginInvoke ("test", Done, null);
 // ...
 //并行其他事情
}
 
static int Work (string s) { return s.Length; }
 
static void Done (IAsyncResult cookie)
{
 var target = (Func<string, int>) cookie.AsyncState;
 int result = target.EndInvoke (cookie);
 Console.WriteLine ("String length is: " + result);
}

EndInvoke做了三件事情:

  1. 等待委托異步的結(jié)束。
  2. 獲取返回值。
  3. 拋出未處理異常給調(diào)用線程。

Task

Task泛型允許有返回值。

如:

static void Main()
{
 // 創(chuàng)建Task并執(zhí)行
 Task<string> task = Task.Factory.StartNew<string>
  ( () => DownloadString ("http://www.baidu.com") ); 
 // 同時(shí)執(zhí)行其他方法
 Console.WriteLine("begin");
 //等待獲取返回值,并且不會(huì)阻塞主線程
 Console.WriteLine(task.Result);
 Console.WriteLine("end");
} 
static string DownloadString (string uri)
{
 using (var wc = new System.Net.WebClient())
  return wc.DownloadString (uri);
}

參考:

http://www.albahari.com/threading/

以上就是C# 線程相關(guān)知識(shí)總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于C# 線程的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論