c#多線程編程基本。本站提示廣大學習愛好者:(c#多線程編程基本)文章只能為提供參考,不一定能成為您想要的結果。以下是c#多線程編程基本正文
不管您是為具有單個處置器的盤算機照樣為具有多個處置器的盤算機停止開辟,您都願望運用法式為用戶供給最好的呼應機能,即便運用法式以後正在完成其他任務。要使運用法式可以或許疾速呼應用戶操作,同時在用戶事宜之間或許乃至在用戶事宜時代應用處置器,最壯大的方法之一是應用多線程技巧。
多線程:線程是法式中一個單一的次序掌握流程.在單個法式中同時運轉多個線程完成分歧的任務,稱為多線程。假如某個線程停止一次長延遲操作, 處置器就切換到另外一個線程履行。如許,多個線程的並行(並發)履行隱蔽了長延遲,進步了處置器資本應用率,從而進步了全體機能。多線程是為了同步完成多項義務,不是為了進步運轉效力,而是為了進步資本應用效力來進步體系的效力
1、過程與線程
過程,是操作體系停止資本調劑和分派的根本單元。是由過程掌握塊、法式段、數據段三部門構成。一個過程可以包括若支線程(Thread),線程可以贊助運用法式同時做幾件事(比 如一個線程向磁盤寫入文件,另外一個則吸收用戶的按鍵操作並實時做出反響,相互不攪擾),在法式被運轉後中,體系起首要做的就是為該法式過程樹立一個默許線程,然後法式可 以依據須要自行添加或刪除相干的線程。它是可並發履行的法式。在一個數據聚集上的運轉進程,是體系停止資本分派和調劑的一個自力單元,也是稱運動、途徑或義務,它有兩方面性質:運動性、並發性。過程可以劃分為運轉、壅塞、停當三種狀況,並隨必定前提而互相轉化:停當--運轉,運轉--壅塞,壅塞--停當。
線程(thread),線程是CPU調劑和履行的最小單元。有時被稱為輕量級過程(Lightweight Process,LWP),是法式履行流的最小單位。一個尺度的線程由線程ID,以後指令指針(PC),存放器聚集和客棧構成。別的,線程是過程中的一個實體,是被體系自力調劑和分配的根本單元,線程本身不具有體系資本,只具有一點在運轉中必弗成少的資本,但它可與同屬一個過程的其它線程同享過程所具有的全體資本。一個線程可以創立和取消另外一個線程,統一過程中的多個線程之間可以並發履行。因為線程之間的互相制約,導致線程在運轉中出現出連續性。線程也有停當、壅塞和運轉三種根本狀況。
主線程,過程創立時,默許創立一個線程,這個線程就是主線程。主線程是發生其他子線程的線程,同時,主線程必需是最初一個停止履行的線程,它完成各類封閉其他子線程的操作。雖然主線程是法式開端時主動創立的,它也能夠經由過程Thead類對象來掌握,經由過程挪用CurrentThread辦法取得以後線程的援用
多線程的優勢:過程有自力的地址空間,統一過程內的線程同享過程的地址空間。啟動一個線程所消費的空間遠遠小於啟動一個過程所消費的空間,並且,線程間彼此切換所需的時光也遠遠小於過程間切換所須要的時光。
2、多線程長處
1、進步運用法式呼應。這對圖形界面的法式特別成心義,當一個操作耗時很長時,全部體系都邑期待這個操作,此時法式不會呼應鍵盤、鼠標、菜單的操作,而應用多線程技巧,將耗時長的操作(time consuming)置於一個新的線程,可以免這類為難的情形。
2、使多CPU體系加倍有用。操作體系會包管當線程數不年夜於CPU數量時,分歧的線程運轉於分歧的CPU上。
3、改良法式構造。一個既長又龐雜的過程可以斟酌分為多個線程,成為幾個自力或半自力的運轉部門,如許的法式會利於懂得和修正。。
多線程雖然優勢顯著,然則線程並發抵觸、同步和治理跟蹤,能夠給體系帶來許多不肯定性,這些必需惹起足夠看重。
空話不多說開端我們的多線程之旅。
3、多線程的運用場所:
簡略總結了一下,普通有兩種情形:
1)多個線程,完成同類義務,進步並發機能
2)一個義務有多個自力的步調,多個線程並發履行各子義務,進步義務處置效力
4、案例--搬運工
在我們實際生涯中,常常看到如許的場景。有一堆貨色,有幾個搬運工擔任將貨色搬運到指定所在。然則搬運工才能分歧,有人一次能搬多箱,有人走路比擬慢,搬運一趟的時光距離比擬長。搬運工,各自搬運,無前後,互不攪擾。我們若何在法式中完成這類場景呢?
案例剖析:
這個就是最簡略的多線程的現實案例。每一個人相當於一個線程,並發履行。當貨色搬運終了後,每一個線程主動停滯。這裡臨時不斟酌逝世鎖情形。
案例代碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace MutiThreadSample.Transport
{
/// <summary>
/// 搬運工
/// </summary>
public class Mover
{
/// <summary>
/// 總數
/// </summary>
public static int GoodsTotal { get; set; }
/// <summary>
/// 距離時光
/// </summary>
public static int IntervalTime { get; set; }
/// <summary>
/// 稱號
/// </summary>
public string Name { get; set; }
/// <summary>
/// 單元時光搬運量
/// </summary>
public int LaborAmount { get; set; }
/// <summary>
/// 搬運
/// </summary>
public void Move()
{
while (GoodsTotal > 0)
{
GoodsTotal -= LaborAmount;
Console.WriteLine("搬運者:{0} 於 {1} 搬運貨色 {2}",this.Name,DateTime.Now.Millisecond,this.LaborAmount);
Thread.Sleep(IntervalTime);
Console.WriteLine("搬運者:{0} Continue",this.Name);
}
}
/// <summary>
/// 搬運
/// </summary>
/// <param name="interval">時光距離</param>
public void Move(object interval)
{
int tempInterval = 0;
if (!int.TryParse(interval.ToString(), out tempInterval))
{
tempInterval = IntervalTime;
}
while (GoodsTotal > 0)
{
GoodsTotal -= LaborAmount;
Console.WriteLine("搬運者:{0} 於 {1} 搬運貨色 {2}", this.Name, DateTime.Now.Millisecond, this.LaborAmount);
Thread.Sleep(tempInterval);
}
}
}
}
測試:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace MutiThreadSample.Transport
{
/// <summary>
/// 測試搬運
/// </summary>
public class TestMove
{
/// <summary>
/// 搬運
/// </summary>
public static void Move()
{
//測試搬運工
Mover.GoodsTotal = 200;
Mover.IntervalTime = 10;
Mover m1 = new Mover() { Name = "Tom", LaborAmount = 5 };
Mover m2 = new Mover() { Name = "Jim", LaborAmount = 10 };
Mover m3 = new Mover() { Name = "Lucy", LaborAmount = 20 };
List<Mover> movers = new List<Mover>();
movers.Add(m1);
//movers.Add(m2);
//movers.Add(m3);
if (movers != null && movers.Count > 0)
{
foreach (Mover m in movers)
{
Thread thread = new Thread(new ThreadStart(m.Move));
thread.Start();
}
}
//Main Thread continue
// validate Thread.Sleep()
//int i =0;
//int j = 0;
//while (i < 10)
//{
// while(j<10000000)
// {
// j++;
// }
// Console.WriteLine("CurrentThread:{0}", Thread.CurrentThread.Name);
// i++;
//}
}
/// <summary>
/// 搬運
/// </summary>
public static void MoveWithParamThread()
{
//測試搬運工
Mover.GoodsTotal = 1000;
Mover.IntervalTime = 100;
Mover m1 = new Mover() { Name = "Tom", LaborAmount = 5 };
Mover m2 = new Mover() { Name = "Jim", LaborAmount = 10 };
Mover m3 = new Mover() { Name = "Lucy", LaborAmount = 20 };
List<Mover> movers = new List<Mover>();
movers.Add(m1);
movers.Add(m2);
movers.Add(m3);
if (movers != null && movers.Count > 0)
{
foreach (Mover m in movers)
{
Thread thread = new Thread(new ParameterizedThreadStart(m.Move));
thread.Start(10);
}
}
}
}
}
經由過程案例我們也接觸了Thread,上面我們將具體引見Thread的功效。
5、Thread
創立並掌握線程,設置其優先級並獲得其狀況。
經常使用辦法:
Start()
招致操作體系將以後實例的狀況更改成 ThreadState.Running。
一旦線程處於 ThreadState.Running 狀況,操作體系便可以支配其履行。 線程從辦法的第一行(由供給給線程結構函數的 ThreadStart 或 ParameterizedThreadStart 拜托表現)開端履行。線程一旦終止,它就沒法經由過程再次挪用 Start 來從新啟動。
Thread.Sleep()
挪用 Thread.Sleep 辦法會招致以後線程立刻阻攔,阻攔時光的長度等於傳遞給 Thread.Sleep 的毫秒數,如許,就會將當時間片中殘剩的部門讓與另外一個線程。 一個線程不克不及針對另外一個線程挪用 Thread.Sleep。
Interrupt()
中止處於 WaitSleepJoin 線程狀況的線程。
Suspend和Resume(已過時)
掛起和持續
在 .NET Framework 2.0 版中,Thread.Suspend 和 Thread.Resume 辦法已標志為過時,並將從將來版本中移除。
Abort()
辦法用於永遠地停滯托管線程。一旦線程被中斷,它將沒法從新啟動。
Join()
壅塞挪用線程,直到某個線程終止時為止。
ThreadPriority(優先級)
指定 Thread 的調劑優先級。
ThreadPriority 界說一組線程優先級的一切能夠值。線程優先級指定一個線程絕對於另外一個線程的絕對優先級。
每一個線程都有一個分派的優先級。在運轉庫內創立的線程最後被分派 Normal 優先級,而在運轉庫外創立的線程在進入運轉庫時將保存其先前的優先級。可以經由過程拜訪線程的 Priority 屬性來獲得和設置其優先級。
依據線程的優先級調劑線程的履行。用於肯定線程履行次序的調劑算法隨操作體系的分歧而分歧。操作體系也能夠在用戶界面的核心在前台和後台之間挪動時靜態地調劑線程的優先級。
一個線程的優先級不影響該線程的狀況;該線程的狀況在操作體系可以調劑該線程之前必需為 Running。
6、創立線程方法
經由過程搬運工案例我們可以或許懂得線程的任務道理,也明確了線程的創立方法。
其其實C#中創立線程有幾種方法,這裡給年夜家舉幾個經常使用例子,以下:
using System;
using System.Threading;
namespace MutiThreadSample
{
/// <summary>
/// 創立線程的方法
/// </summary>
class CreateThread
{
/// <summary>
/// 不帶參數的拜托
/// </summary>
public void CreateThreadWithThreadStart()
{
Thread thread = new Thread(new ThreadStart(ThreadCallBack));
thread.Start();
}
/// <summary>
/// 帶參數的拜托
/// </summary>
public void CreateThreadWithParamThreadStart()
{
Thread thread = new Thread(new ParameterizedThreadStart(ThreadCallBackWithParam));
thread.Start();
}
/// <summary>
/// 匿名函數
/// </summary>
public void CreateThreadWithAnonymousFunction()
{
Thread thread = new Thread(delegate()
{
Console.WriteLine("進入子線程1");
for (int i = 1; i < 4; ++i)
{
Thread.Sleep(50);
Console.WriteLine("\t+++++++子線程1+++++++++");
}
Console.WriteLine("加入子線程1");
});
thread.Start();
}
/// <summary>
/// 直接賦值拜托
/// </summary>
public void CreateThreadWithCallBack()
{
Thread _hThread = new Thread(ThreadCallBack);
_hThread.Start();
}
/// <summary>
/// 無參數的辦法挪用
/// </summary>
public void ThreadCallBack()
{
// Do Something
}
/// <summary>
/// 帶參數的辦法
/// </summary>
/// <param name="obj"></param>
public void ThreadCallBackWithParam(object obj)
{
// Do Something
}
}
}
時鐘線程
應用 TimerCallback 拜托指定願望 Timer 履行的辦法。 計時器拜托在結構計時器時指定,而且不克不及更改。 此辦法不在創立計時器的線程上履行,而是在體系供給的 ThreadPool 線程上履行。創立計時器時,可以指定在第一次履行辦法之前期待的時光量(截止時光)和爾後的履行時代期待的時光量(時光周期)。 可使用 Change 辦法更改這些值或禁用計時器。
using System;
using System.Threading;
class TimerExample
{
static void Main()
{
// Create an event to signal the timeout count threshold in the
// timer callback.
AutoResetEvent autoEvent = new AutoResetEvent(false);
StatusChecker statusChecker = new StatusChecker(10);
// Create an inferred delegate that invokes methods for the timer.
TimerCallback tcb = statusChecker.CheckStatus;
// Create a timer that signals the delegate to invoke
// CheckStatus after one second, and every 1/4 second
// thereafter.
Console.WriteLine("{0} Creating timer.\n",
DateTime.Now.ToString("h:mm:ss.fff"));
Timer stateTimer = new Timer(tcb, autoEvent, 1000, 250);
// When autoEvent signals, change the period to every
// 1/2 second.
autoEvent.WaitOne(5000, false);
stateTimer.Change(0, 500);
Console.WriteLine("\nChanging period.\n");
// When autoEvent signals the second time, dispose of
// the timer.
autoEvent.WaitOne(5000, false);
stateTimer.Dispose();
Console.WriteLine("\nDestroying timer.");
}
}
class StatusChecker
{
private int invokeCount;
private int maxCount;
public StatusChecker(int count)
{
invokeCount = 0;
maxCount = count;
}
// This method is called by the timer delegate.
public void CheckStatus(Object stateInfo)
{
AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
Console.WriteLine("{0} Checking status {1,2}.",
DateTime.Now.ToString("h:mm:ss.fff"),
(++invokeCount).ToString());
if(invokeCount == maxCount)
{
// Reset the counter and signal Main.
invokeCount = 0;
autoEvent.Set();
}
}
}
7、前台線程和後台線程
.Net的公用說話運轉時(Common Language Runtime,CLR)能辨別兩種分歧類型的線程:前台線程和後台線程。這二者的差別就是:運用法式必需運轉完一切的前台線程才可以加入;而關於後台線程,運用法式則可以不斟酌其能否曾經運轉終了而直接加入,一切的後台線程在運用法式加入時都邑主動停止。
一個線程是前台線程照樣後台線程可由它的IsBackground屬性來決議。這個屬性是可讀又可寫的。它的默許值為false,即意味著一個線程默許為前台線程。
我們可以將它的IsBackground屬性設置為true,從而使之成為一個後台線程。上面的例子是一個掌握台法式,法式一開端便啟動了10個線程,每一個線程運轉5秒鐘時光。因為線程的IsBackground屬性默許為false,即它們都是前台線程,所以雖然法式的主線程很快就運轉停止了,但法式要到一切已啟動的線程都運轉終了才會停止。示例代碼以下例子中的Test()所示
using System;
using System.Threading;
namespace MutiThreadSample.ThreadType
{
class ThreadTypeTest
{
/// <summary>
/// 測試前台線程
/// </summary>
public static void Test()
{
for (int i = 0; i < 10; i++)
{
Thread thread = new Thread(new ThreadStart(ThreadFunc));
thread.Start();
}
}
/// <summary>
/// 測試後台線程
/// </summary>
public static void TestBackgroundThread()
{
for (int i = 0; i < 10; i++)
{
Thread thread = new Thread(new ThreadStart(ThreadFunc));
thread.IsBackground = true;
thread.Start();
}
}
public static void ThreadFunc()
{
Thread.Sleep(0);
DateTime start = DateTime.Now;
while ((DateTime.Now - start).Seconds < 20);//可以停留的時光長一點,後果加倍顯著
}
}
}
接上去我們對下面的代碼停止稍微修正,將每一個線程的IsBackground屬性都設置為true,則每一個線程都是後台線程了。那末只需法式的主線程停止了,全部法式也就停止了。示例代碼如代碼中的TestBackgroundThread()。
這個例子直接創立一個掌握台法式便可磨練。
前台和後台線程的應用准繩
既然前台線程和後台線程有這類差異,那末我們怎樣曉得該若何設置一個線程的IsBackground屬性呢?上面是一些根本的准繩:關於一些在後台運轉的線程,當法式停止時這些線程沒有需要持續運轉了,那末這些線程就應當設置為後台線程。好比一個法式啟動了一個停止年夜量運算的線程,可是只需法式一旦停止,誰人線程就掉去了持續存在的意義,那末誰人線程就該是作為後台線程的。而關於一些辦事於用戶界面的線程常常是要設置為前台線程的,由於即便法式的主線程停止了,其他的用戶界面的線程極可能要持續存在來顯示相干的信息,所以不克不及立刻終止它們。這裡我只是給出了一些准繩,詳細到現實的應用常常須要編程者的進一步細心推敲。
8、總結
這一章重要引見多線程技巧的根本常識。觸及多線程的詳細運用,包含預防逝世鎖、線程同步、線程池等,在往後的文章會觸及到。