C#多線程
一多線程的幾種方式
統(tǒng)一用于測試的模擬下載代碼
private static void DownMethod(int id,string fileName)
{
Console.WriteLine("開始下載!文件ID:{0},文件名字{1}", id, fileName);
for (int i = 0; i < 100; i+=10)
{
Thread.Sleep(10);
Console.WriteLine("已經(jīng)下載{0}", i);
}
Console.WriteLine("下載完畢?。?!");
}
1:委托創(chuàng)建線程實現(xiàn)異步
實例1:Action委托創(chuàng)建線程
//多線程的第一種方式Action委托沒有返回值
public static void ThreadMethod(int id, string fileName)
{
DownMethod(id,fileName);
}
static void Main(string[] args)
{
//方法一:使用Action委托來異步執(zhí)行方法
Action<int, string> fileDownAction = ThreadMethod;
//前面的參數(shù)是委托所需要的參數(shù),后面兩個是固定的,一個是線程結(jié)束回調(diào),一個是傳入回調(diào)的參數(shù)
fileDownAction.BeginInvoke(1, "天才在左瘋子在右", ar =>
{
Console.WriteLine("線程結(jié)束回調(diào)");
Console.WriteLine(ar.AsyncState as string);//最后一個參數(shù)
fileDownAction.EndInvoke(ar);//這個方法用于獲取此線程的返回值,由于Action委托沒有返回值,所以不返回
}, "回調(diào)的參數(shù)");
Console.WriteLine("我是主線程");
Console.ReadKey();
}
結(jié)果
我是主線程
開始下載!文件ID:1,文件名字天才在左瘋子在右
已經(jīng)下載0
已經(jīng)下載10
已經(jīng)下載20
已經(jīng)下載30
已經(jīng)下載40
已經(jīng)下載50
已經(jīng)下載60
已經(jīng)下載70
已經(jīng)下載80
已經(jīng)下載90
下載完畢?。?!
線程結(jié)束回調(diào)
回調(diào)的參數(shù)
實例2:Func委托創(chuàng)建線程
public static string FuncThreadMethod(int id, string fileName)
{
DownMethod(id, fileName);
return "異步方法的返回值?。?!";
}
static void Main(string[] args)
{
Func<int, string,string> fileDownAction = FuncThreadMethod;
//前面的參數(shù)是委托所需要的參數(shù),后面兩個是固定的,一個是線程結(jié)束回調(diào),一個是傳入回調(diào)的參數(shù)
fileDownAction.BeginInvoke(1, "天才在左瘋子在右", ar =>
{
Console.WriteLine("線程結(jié)束回調(diào)");
Console.WriteLine(ar.AsyncState as string);//最后一個參數(shù)
string result = fileDownAction.EndInvoke(ar);//這個方法用于獲取此線程的返回值,由于Action委托沒有返回值,所以不返回
Console.WriteLine(result);
}, "回調(diào)的參數(shù)");
Console.WriteLine("我是主線程");
Console.ReadKey();
}
}
我是主線程
開始下載!文件ID:1,文件名字天才在左瘋子在右
已經(jīng)下載0
已經(jīng)下載10
已經(jīng)下載20
已經(jīng)下載30
已經(jīng)下載40
已經(jīng)下載50
已經(jīng)下載60
已經(jīng)下載70
已經(jīng)下載80
已經(jīng)下載90
下載完畢!??!
線程結(jié)束回調(diào)
回調(diào)的參數(shù)
異步方法的返回值?。。?
2:Thread創(chuàng)建異步線程
實例1:普通的方式
//使用Thread方式創(chuàng)建的委托方法一定沒有返回值,參數(shù)可有可無,但是有參數(shù)的話一定是object類型的
public static void AsyncWithThreadMethod(object obj)
{
Console.WriteLine("開始下載!文件名字{0}", obj as string);
for (int i = 0; i < 100; i += 10)
{
Thread.Sleep(10);
Console.WriteLine("已經(jīng)下載{0}", i);
}
Console.WriteLine("下載完畢!?。?);
}
static void Main(string[] args)
{
//Thread創(chuàng)建線程的方式,Thread的構(gòu)造參數(shù)一定是個無返回值的委托
Thread thread = new Thread(AsyncWithThreadMethod);
thread.Start("天才在左瘋子在右");
Console.WriteLine("我是主線程");
Console.ReadKey();
}
lambda表達式方式:
Thread thread = new Thread((obj) =>
{
Console.WriteLine("開始下載!文件名字{0}", obj as string);
for (var i = 0; i < 100; i += 10)
{
Thread.Sleep(10);
Console.WriteLine("已經(jīng)下載{0}", i);
}
Console.WriteLine("下載完畢?。?!");
});
thread.Start("天才在左瘋子在右");
但是這種方式如果想要傳遞一些復(fù)雜的或者多個參數(shù)就不好弄了,所以可以用另一種方式,自己新建一個類來處理
class MyThread
{
private int id;
private string name;
public MyThread(int id, string name)
{
this.id = id;
this.name = name;
}
private void DownFileStart(object callback)
{
Action<string> action = callback as Action<string>;
Console.WriteLine("開始下載!文件ID:{0},文件名字{1}", id, name);
for (int i = 0; i < 100; i += 10)
{
Thread.Sleep(10);
Console.WriteLine("已經(jīng)下載{0}", i);
}
Console.WriteLine("下載完畢?。。?);
action?.Invoke("下載完畢?。。。?);
}
//這個方法使用回調(diào)來處理線程執(zhí)行完畢的結(jié)果
public void Start(Action<string> callback)
{
Thread t = new Thread(DownFileStart);
t.Start(callback);
}
}
使用:
MyThread mt = new MyThread(1, "aaa");
mt.Start(o =>
{
Console.WriteLine("線程完成回調(diào)---->{0}", o);
});
Console.WriteLine("我是主線程");
Console.ReadKey();
結(jié)果:
我是主線程
開始下載!文件ID:1,文件名字天才在左瘋子在右
已經(jīng)下載0
已經(jīng)下載10
已經(jīng)下載20
已經(jīng)下載30
已經(jīng)下載40
已經(jīng)下載50
已經(jīng)下載60
已經(jīng)下載70
已經(jīng)下載80
已經(jīng)下載90
下載完畢?。。?線程完成回調(diào)---->下載完畢?。。?!
前臺線程和后臺線程
前臺線程:Thread創(chuàng)建的線程默認(rèn)都是前臺線程,前臺線程不會跟隨主線程的停止而停止,如果主線程提前停止,程序會跟著前臺線程的停止而停止
后臺線程:使用線程池創(chuàng)建的線程都是后臺線程,不會被更改,主線程停止了后臺線程也就隨之停止了,Thread可以設(shè)置IsBackground = true;來設(shè)置為后臺線程
3:線程池創(chuàng)建線程
線程池類ThreadPool是靜態(tài)類,無法被聲明和new,只能通過類名來調(diào)用里面的靜態(tài)方法
一般通過線程池創(chuàng)建線程的方法如下:
ThreadPool.QueueUserWorkItem(state =>
{
Console.WriteLine("創(chuàng)建線程池的線程,參數(shù):{0},線程ID:{1}", state as string, Thread.CurrentThread.ManagedThreadId);
}, "11");
一般來說線程池用于處理多個耗時較為少的任務(wù),不推薦處理長時間任務(wù)
4:任務(wù)(Task)
(模擬線程代碼)
private static void DownMethod(int id, string fileName)
{
Console.WriteLine("開始下載!文件ID:{0},文件名字{1}", id, fileName);
for (int i = 0; i < 100; i += 10)
{
Thread.Sleep(10);
Console.WriteLine("已經(jīng)下載{0}", i);
}
Console.WriteLine("下載完畢?。?!");
}
//多線程的第一種方式Action委托沒有返回值
public static string ThreadMethod()
{
DownMethod(1, "天才在左瘋子在右");
return "1111";
}
public static void SaveInFile(Task task)
{
Console.WriteLine("正在存儲文件");
Thread.Sleep(1000);
Console.WriteLine("存儲完畢!??!");
}
任務(wù)有兩種創(chuàng)建方式
- 泛型代表返回值
Task<string> t = new Task<string>(ThreadMethod);
t.Start();
TaskFactory<string> tf = new TaskFactory<string>();
tf.StartNew(ThreadMethod);
連續(xù)任務(wù)
當(dāng)任務(wù)1依賴于任務(wù)2的時候那就需要任務(wù)2執(zhí)行完畢之后執(zhí)行任務(wù)1,這個時候用連續(xù)任務(wù)
TaskFactory<string> tf = new TaskFactory<string>();
var startNew = tf.StartNew(ThreadMethod);
//主要是這句話
var startNew2 = startNew.ContinueWith(SaveInFile);
任務(wù)的層次結(jié)構(gòu)
如果在一個任務(wù)中再次調(diào)用一個任務(wù),那么內(nèi)部這個任務(wù)就是外邊的任務(wù)的子任務(wù),如果子任務(wù)沒有執(zhí)行完畢而父任務(wù)執(zhí)行完畢的話,則父任務(wù)的狀態(tài)是WaitingForChildrenToComplete,只有子任務(wù)執(zhí)行完了,父任務(wù)的狀態(tài)就變成了RunToComplete
4:線程爭用問題(同步鎖)
測試類:
/// <summary>
/// 測試線程爭用問題
/// </summary>
class MyThreadObject
{
//用于測試多線程爭用同一變量的變量
private int state = 5;
public void ChangeState()
{
state++;
if (state == 5)
{
Console.WriteLine("state =5");//正常來講是永遠不會執(zhí)行這一句的
}
state = 5;//當(dāng)其中一個線程執(zhí)行到這一句的時候另一個線程剛執(zhí)行到if (state == 5),這時就會打印
}
}
主函數(shù):
class Program
{
static void ChangeMyThreadState(object obj)
{
var myThread = obj as MyThreadObject;
while (true)
{
myThread?.ChangeState();
}
}
private static void Main(string[] args)
{
MyThreadObject myThread = new MyThreadObject();
Thread t1 = new Thread(ChangeMyThreadState);
t1.Start(myThread); //只有這一個線程是沒有問題的
Thread t2 = new Thread(ChangeMyThreadState);
t2.Start(myThread); //加上這個線程就會出問題,
Console.WriteLine("我是主線程");
Console.ReadKey();
}
}
結(jié)果是打印了好多。
解決方法是加同步鎖
static void ChangeMyThreadState(object obj)
{
var myThread = obj as MyThreadObject;
while (true)
{
if (myThread == null) continue;
lock (myThread) //像系統(tǒng)申請鎖定需要調(diào)用的對象,如果另一個線程執(zhí)行到這發(fā)現(xiàn)被鎖定了則會等待鎖定結(jié)束,然后繼續(xù)執(zhí)行
{
myThread.ChangeState(); //在同一時刻只有一個線程執(zhí)行
}
}
}
注意:lock只能鎖定對象(引用類型)
4:線程死鎖
class Program
{
static MyThreadObject myThread = new MyThreadObject();
static MyThreadObject myThread1 = new MyThreadObject();
static void ChangeMyThreadState(object obj)
{
while (true)
{
lock (myThread) //像系統(tǒng)申請鎖定需要調(diào)用的對象,如果另一個線程執(zhí)行到這發(fā)現(xiàn)被鎖定了則會等待鎖定結(jié)束,然后繼續(xù)執(zhí)行
{
lock (myThread1)
{
myThread.ChangeState(); //在同一時刻只有一個線程執(zhí)行
Console.WriteLine("0000");
}
}
}
}
static void ChangeMyThreadState1(object obj)
{
while (true)
{
lock (myThread1) //像系統(tǒng)申請鎖定需要調(diào)用的對象,如果另一個線程執(zhí)行到這發(fā)現(xiàn)被鎖定了則會等待鎖定結(jié)束,然后繼續(xù)執(zhí)行
{
lock (myThread)
{
myThread.ChangeState(); //在同一時刻只有一個線程執(zhí)行
Console.WriteLine("11111");
}
}
}
}
private static void Main(string[] args)
{
Thread t1 = new Thread(ChangeMyThreadState);
t1.Start(); //只有這一個線程是沒有問題的
Thread t2 = new Thread(ChangeMyThreadState1);
t2.Start(); //加上這個線程就會出問題,
Console.WriteLine("我是主線程");
Console.ReadKey();
}
}
執(zhí)行結(jié)果只打印了10幾條,就停止了