如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結(jié)果和單線程運行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的。
或者說:一個類或者程序所提供的接口對于線程來說是原子操作或者多個線程之間的切換不會導(dǎo)致該接口的執(zhí)行結(jié)果存在二義性,也就是說我們不用考慮同步的問題。
線程安全問題都是由全局變量及靜態(tài)變量引起的。
若每個線程中對全局變量、靜態(tài)變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時對一個變量執(zhí)行讀寫操作,一般都需要考慮線程同步,否則就可能影響線程安全。
lock的目的是防止多線程執(zhí)行的時候出現(xiàn)并發(fā)操作問題,加上lock的引用類型的對象,在其鎖定的區(qū)域內(nèi),在一個時刻只允許一個線程操作。
lock只能鎖定一個引用類型變量,也就是鎖定一個地址
class Program
{
static void Main(string[] args)
{
ThreadA t = new ThreadA();
#region Thread
ThreadA.obj.i = 10;
Thread th1 = new Thread(new ThreadStart(t.hhh));
th1.Name = "Th1";
th1.Start();
Thread th2 = new Thread(new ThreadStart(t.hhh));
th2.Name = "Th2";
th2.Start();
#endregion
#region Task
ThreadA.obj.i = 10;
Task t1 = new Task(t.hhh);
t1.Start();
Task t2 = new Task(t.hhh);
t2.Start();
#endregion
Console.WriteLine("Hello World!");
Console.WriteLine();
Console.ReadKey();
}
}
class ThreadA
{
public static IntI obj = new IntI();
public void hhh()
{
lock (obj)
{
for (int i = 0; i < 7; i++)
{
Thread.Sleep(500);
if (obj.i > 0)
{
obj.i--;
Console.WriteLine("當前線程名: " + Thread.CurrentThread.ManagedThreadId+ ",obj.i= " + obj.i);
}
}
}
}
}
class IntI
{
public int i;
}
加鎖和不加鎖運行的結(jié)果有區(qū)別 :
加鎖后:i的值會一個個遞減,不會出現(xiàn)跳躍,不會出現(xiàn)重復(fù)輸出,一直到0值;
不加鎖:i的值輸出會出現(xiàn)跳躍,不連續(xù)遞減,可能還會出現(xiàn)-1值輸出;
原因:加鎖后,一個時刻只能有一個線程執(zhí)行被鎖區(qū)域的代碼,兩個線程都是有先后順序執(zhí)行的,所以不會出現(xiàn)間斷輸出。
Task是用來實現(xiàn)多線程的類,在以前當版本中已經(jīng)有了Thread及ThreadPool,為什么還要提出Task類呢,這是因為直接操作Thread及ThreadPool,向線程中傳遞參數(shù),獲取線程的返回值及線程當啟停都非常的麻煩,所以微軟的工程師對Thread進行了再封裝,這就是Task,可以這么說Task是架構(gòu)在Thread之上的,
所以多線程時Task是我們的首選。
Task類和Task<TResult>類,后者是前者的泛型版本。TResult類型為Task所調(diào)用方法的返回值。
主要區(qū)別在于Task構(gòu)造函數(shù)接受的參數(shù)是Action委托,而Task<TResult>接受的是Func<TResult>委托
- Task的聲明
Task的聲明有兩種方式:
a,通過new 的方式來聲明
Task objTask = new Task();
b.通過Task.Factory.StartNew的方式來聲明
Task.Factory.StartNew(MyMethod);
這兩種聲明方式的區(qū)別,第一種聲明方式開啟線程必須使用objTask.Start(),而通過Task.Factory.StartNew的方式則不用。
class Program
{
static void Main(string[] args)
{
Console.WriteLine("調(diào)用主線程");
//Task objTask = new Task(() => Console.WriteLine("Task1"));
//objTask.Start();
//new Task(() => Console.WriteLine("Task2")).Start();
//Task t1 = Task.Run(() => { Thread.Sleep(3000); Console.WriteLine("Task3"); });
//t1.Wait();
//Task t2 = Task.Run<string>(() => "wwmin");
//bool b2 = t2.Wait(2000);
//Thread.Sleep(4000);
#region getWaiter and continueWith
Task<int> TaskInt1 = Task.Run<int>(() =>
{
Thread.Sleep(2000);
return Enumerable.Range(1, 100).Sum();
});
var awaiter = TaskInt1.GetAwaiter();
awaiter.OnCompleted(() =>
{
Console.WriteLine("TaskInt1 finished");
int result = awaiter.GetResult();
Console.WriteLine(result);
});
TaskInt1.ContinueWith(antecedent =>
{
Console.WriteLine(antecedent.Result);
Console.WriteLine("Running continue Task");
});
#endregion
Task<string> s = TestAsync();
Console.WriteLine(s.Result);
Console.WriteLine("Hello World!");
Console.ReadKey();
}
static async Task<string> TestAsync()
{
Console.WriteLine("運行task之前" + Thread.CurrentThread.ManagedThreadId);
Task<string> t = Task.Run<string>(() =>
{
Console.WriteLine("運行Task" + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(2000);
return "我是測試線程";
});
Console.WriteLine("運行Task之后" + Thread.CurrentThread.ManagedThreadId);
var result = await t;
return result;
}
}
延遲任務(wù)
Task.Delay()方法是相當于異步的Thread.Sleep();
要獲得返回值,就要用到Task的泛型版本了。 說到Task的返回值就不得不說await和async關(guān)鍵字了。
當函數(shù)使用async標記后,返回值必須為void,Task,Task<T>,當返回值為Task<T>時,函數(shù)內(nèi)部只需要返回T類型,編譯器會自動包裝成Task<T>類型
await關(guān)鍵字必須在具有async標記的函數(shù)內(nèi)使用。
(1) 在async標識的方法體里面,如果沒有await關(guān)鍵字的出現(xiàn),那么這種方法和調(diào)用普通的方法沒什么區(qū)別(就是說async和await是成對出現(xiàn)的,沒有await的async是沒有意義的)
(2)在async標識的方法體里面,在await關(guān)鍵字出現(xiàn)之前,還是主線程順序調(diào)用的,直到await關(guān)鍵字的出現(xiàn)才會出現(xiàn)線程阻塞。
(3)await關(guān)鍵字可以理解為等待方法執(zhí)行完畢,除了可以標記有async關(guān)鍵字的方法外,還能標記Task對象,表示等待該線程執(zhí)行完畢。所以await關(guān)鍵字并不是針對于async的方法,而是針對async方法所返回給我們的Task。
延續(xù)任務(wù),就是說在任務(wù)執(zhí)行完成之后繼續(xù)執(zhí)行任務(wù),有兩種方法
第一種,使用一種是使用GetAwaiter方法。GetAwaiter方法返回一個TaskAwaiter結(jié)構(gòu),該結(jié)構(gòu)有一個OnCompleted事件,只需對OnCompleted事件賦值,即可在完成后調(diào)用該事件。