C#多線程

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)建方式

  1. 泛型代表返回值
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幾條,就停止了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 此篇文章簡單總結(jié)了C#中主要的多線程實現(xiàn)方法,包括Thread、ThreadPool、Parallel和Task類...
    _xinchen閱讀 19,940評論 4 4
  • 1.軟件片段的架構(gòu)是一套控制軟件操作的規(guī)則、模式、進程、執(zhí)行協(xié)議和斷言。 多線程架構(gòu):一種將工作模式分解為兩個或更...
    程序愛好者閱讀 4,456評論 0 0
  • 異步委托 投票,并檢查委托是否完成了任務(wù)所創(chuàng)建的Delegate類提供了BeginInvoke()方法,該方法中,...
    北冥冰皇閱讀 431評論 0 2
  • 原文地址:Threading in C# part 1 : 概念 當(dāng)線程開始執(zhí)行(thread.Start()),...
    fat___lin閱讀 306評論 0 0
  • 姓名夏鋼,無錫夏利達公司 【日精進打卡第141天】 【知~學(xué)習(xí)】 《六項精進》2遍共2遍 《大學(xué)》1遍共1遍 【經(jīng)...
    Atun0219閱讀 110評論 0 0

友情鏈接更多精彩內(nèi)容