" />

最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C#多線程的相關操作講解

 更新時間:2022年03月20日 15:24:17   作者:.NET開發(fā)菜鳥  
本文詳細講解了C#多線程的相關操作,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下

一、線程異常

我們在單線程中,捕獲異??梢允褂胻ry-catch,代碼如下所示:

using System;

namespace MultithreadingOption
{
    class Program
    {
        static void Main(string[] args)
        {
            #region 單線程中捕獲異常
            try
            {
                int[] array = { 1, 23, 61, 678, 23, 45 };
                Console.WriteLine(array[6]);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"message:{ex.Message}");
            }
            #endregion


            Console.ReadKey();
        }
    }
}

程序運行結果:

那么在多線程中如何捕獲異常呢?是不是也可以使用try-catch進行捕獲?我們先看下面的代碼:

using System;
using System.Threading.Tasks;

namespace MultithreadingOption
{
    class Program
    {
        static void Main(string[] args)
        {
            #region 單線程中捕獲異常
            //try
            //{
            //    int[] array = { 1, 23, 61, 678, 23, 45 };
            //    Console.WriteLine(array[6]);
            //}
            //catch (Exception ex)
            //{
            //    Console.WriteLine($"message:{ex.Message}");
            //}
            #endregion

            #region 多線程中的異常

            try
            {
                for (int i = 0; i < 30; i++)
                {
                    string str = $"main_{i}";
                    // 開啟線程
                    Task.Run(() => 
                    {
                        Console.WriteLine($"{str} 開始了");
                        if(str.Equals("main_5"))
                        {
                            throw new Exception("main_5 發(fā)生了異常");
                        }
                        else if (str.Equals("main_11"))
                        {
                            throw new Exception("main_11 發(fā)生了異常");
                        }
                        else if (str.Equals("main_18"))
                        {
                            throw new Exception("main_18 發(fā)生了異常");
                        }
                        Console.WriteLine($"{str} 結束了");

                    });
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"message:{ex.Message}");
            }


            #endregion

            Console.ReadKey();
        }
    }
}

程序運行結果:

我們看到結果中并沒有輸出異常信息,是不是沒有拋出異常呢?我們起代碼進行調試,看調試信息:

我們看到程序中確實也拋出了異常,但是程序卻沒有捕獲到,那么異常去哪里了呢?異常被多線程給吞掉了,那么如何在多線程中捕獲異常呢?如果把try-catch寫在線程里面呢?每一個線程都是單線程的,把try-catch寫在每一個線程里面就沒有意義了。在多線程中捕獲異常,需要使用到WaitAll(),看下面的代碼:

try
{
     // 定義一個Task類型的List集合
     List<Task> taskList = new List<Task>();
     for (int i = 0; i < 30; i++)
     {
            string str = $"main_{i}";
            // 開啟線程,并把線程添加到集合中
            taskList.Add(Task.Run(() =>
            {
                 Console.WriteLine($"{str} 開始了");
                 if (str.Equals("main_5"))
                 {
                     throw new Exception("main_5 發(fā)生了異常");
                 }
                 else if (str.Equals("main_11"))
                 {
                      throw new Exception("main_11 發(fā)生了異常");
                 }
                 else if (str.Equals("main_18"))
                 {
                       throw new Exception("main_18 發(fā)生了異常");
                 }
                 Console.WriteLine($"{str} 結束了");
          }));
      }

     // 等待所有線程都執(zhí)行完
     Task.WaitAll(taskList.ToArray());
}
catch (Exception ex)
{
      Console.WriteLine($"message:{ex.Message}");
}

我們用代碼進行調試,調試結果:

這時就可以進入到catch里面了,我們監(jiān)視ex,發(fā)現ex是AggregateException類型的異常,我們在進一步優(yōu)化代碼:

try
{
     // 定義一個Task類型的List集合
     List<Task> taskList = new List<Task>();
     for (int i = 0; i < 30; i++)
     {
            string str = $"main_{i}";
            // 開啟線程,并把線程添加到集合中
            taskList.Add(Task.Run(() =>
            {
                 Console.WriteLine($"{str} 開始了");
                 if (str.Equals("main_5"))
                 {
                     throw new Exception("main_5 發(fā)生了異常");
                 }
                 else if (str.Equals("main_11"))
                 {
                      throw new Exception("main_11 發(fā)生了異常");
                 }
                 else if (str.Equals("main_18"))
                 {
                       throw new Exception("main_18 發(fā)生了異常");
                 }
                 Console.WriteLine($"{str} 結束了");
          }));
      }

     // 等待所有線程都執(zhí)行完
     Task.WaitAll(taskList.ToArray());
}
catch(AggregateException are)
{
     foreach (var exception in are.InnerExceptions)
     {
          Console.WriteLine(exception.Message);
     }
}
catch (Exception ex)
{
      Console.WriteLine($"message:{ex.Message}");
}

最后運行程序:

我們發(fā)現這時就可以捕獲到具體的異常信息了。

二、線程取消

在上面的示例中,我們捕獲到了多線程中發(fā)生的異常,并且也輸出了異常信息,但是這樣是不友好的。在實際開發(fā)中,我們使用多線程并發(fā)執(zhí)行任務,假如其中某一個任務失敗了或者發(fā)生了異常,我們希望可以通知其他的線程,都停止下來,那么該如何做呢?這時就需要使用到線程取消。

Task不能外部終止任務,只能自己終止自己。

.Net框架提供了CancellationTokenSource類,該類里面有一個bool類型的屬性:IsCancellationRequested,默認是false,表示是否取消線程。還提供了一個Cancel()方法,該方法可以把IsCancellationRequested的屬性值設置為true,并且不能在設置回去。代碼如下:

// 實例化對象
CancellationTokenSource cts = new CancellationTokenSource();

for (int i = 0; i < 20; i++)
{
      string str = $"main_{i}";
      // 開啟線程
      Task.Run(() =>
      {
             try
             {
                  Console.WriteLine($"{str} 開始了");
                  // 暫停
                  Thread.Sleep(new Random().Next(50, 100) * 100);
                  if (str.Equals("main_5"))
                  {
                       throw new Exception("main_5 發(fā)生了異常");
                  }
                  else if (str.Equals("main_11"))
                  {
                        throw new Exception("main_11 發(fā)生了異常");
                  }
                  if (cts.IsCancellationRequested == false)
                  {
                        Console.WriteLine($"{str} 結束了");
                  }
                  else
                  {
                         Console.WriteLine($"{str} 線程取消");
                  }

            }
            catch (Exception ex)
            {
                   // 發(fā)生了異常,將IsCancellationRequested的值設置為true
                   cts.Cancel();
                   Console.WriteLine($"message:{ex.Message}");
            }
     });
}

程序運行結果:

可以看到,當有異常發(fā)生之后,有的線程就被取消了。這樣就初步實現了線程取消。

在上面的示例中,我們是先開啟了線程,如果發(fā)生了異常,則取消線程。那么會有這樣一種情況:線程中發(fā)生了異常,可能這時候有的線程還沒有開啟,那么能不能就不讓這些線程在開啟呢?Task的Run方法有一個重載:

第二個參數就表示取消線程。而且CancellationTokenSource類里面正好有這個參數:

所以,我們可以利用Run方法的重載來實現不開啟線程,代碼如下:

try
{
    // 實例化對象
    CancellationTokenSource cts = new CancellationTokenSource();
    // 創(chuàng)建Task類型的集合
    List<Task> taskList = new List<Task>();
    for (int i = 0; i < 20; i++)
    {
        string str = $"main_{i}";
        // 開啟線程 Task.run 以后 添加Token 就可以在某一個線程發(fā)生異常之后,讓沒有開啟的線程不開啟了
        taskList.Add(Task.Run(() =>
        {
            try
            {
                Console.WriteLine($"{str} 開始了");
                // 暫停
                Thread.Sleep(new Random().Next(50, 100) * 10);
                if (str.Equals("main_5"))
                {
                    throw new Exception("main_5 發(fā)生了異常");
                }
                else if (str.Equals("main_11"))
                {
                    throw new Exception("main_11 發(fā)生了異常");
                }
                if (cts.IsCancellationRequested == false)
                {
                    Console.WriteLine($"{str} 結束了");
                }
                else
                {
                    Console.WriteLine($"{str} 線程取消");
                }

            }
            catch (Exception ex)
            {
                // 發(fā)生了異常,將IsCancellationRequested的值設置為true
                cts.Cancel();
            }

        }, cts.Token));
    }

    // 等待所有線程執(zhí)行完
    Task.WaitAll(taskList.ToArray());
}
catch (AggregateException are)
{
    foreach (var exception in are.InnerExceptions)
    {
        Console.WriteLine(exception.Message);
    }
}

程序運行結果:

輸出結果中有一句話:已取消一個任務,但是我們的代碼里面沒有打印這句話,這是從哪里來的呢?這是因為第二個參數Token的原因,加了這個參數以后,如果就線程發(fā)生了異常,就不在繼續(xù)開啟線程。

三、臨時變量

我們先來看看下面一段代碼:

for (int i = 0; i < 20; i++)
{
    // 開啟線程
    Task.Run(() =>
    {
        Task.Run(() => Console.WriteLine($"this is {i}  ThreadId: {Thread.CurrentThread.ManagedThreadId.ToString("00")}"));
    });
}

這段代碼的輸出結果是什么呢?我們運行程序查看結果:

可能有人會感到疑惑:為什么輸出的都是20呢,而不是每次循環(huán)變量的值?這是什么原因呢。這是因為我們申請線程的時候不會發(fā)生阻塞,而且還是延遲執(zhí)行的。我們知道,代碼的執(zhí)行速度是非常快的,循環(huán)20次幾乎一瞬間就完成了,這是i就變成了20,但是線程是延遲執(zhí)行的,當線程真正去執(zhí)行的時候,對應的是同一個i,這時i是20,所以輸出的都是20。那么該如何輸出每次循環(huán)的值呢?看下面的代碼:

for (int i = 0; i < 20; i++)
{
    // 定義一個新的變量
    int k = i;
    // 開啟線程
    Task.Run(() =>
    {
        Task.Run(() => Console.WriteLine($"this is {i}_{k}  ThreadId: {Thread.CurrentThread.ManagedThreadId.ToString("00")}"));
    });
}

程序運行結果:

這樣每次循環(huán)的時候,都重新定義變量k,保證每次都是全新的,所以k的值就是每次循環(huán)的值。

四、線程安全

什么是線程安全呢?線程安全:如果你的代碼在進程中有多個線程同時運行這一段,如果每次運行的結果都跟單線程運行時的結果一致,那么就是線程安全的。

在什么情況下會出現線程安全的問題呢?

一般都是有全局變量/共享變量/靜態(tài)變量/硬盤文件/數據庫的值,只要多線程訪問和修改,就會出現線程安全的問題。看下面的代碼:

int syncNum = 0;

int AsyncNum = 0;
for (int i = 0; i < 10000; i++)
{
    syncNum++;
}
Console.WriteLine($"syncNum={syncNum}"); //單線程10000   10000

for (int i = 0; i < 10000; i++)
{
    Task.Run(() =>
    {
        AsyncNum++;
    });
}
Console.WriteLine($"AsyncNum ={AsyncNum}");

程序運行結果:

這就是線程安全造成的問題。那么該如何解決這個問題呢?這時可以使用lock關鍵字解決。lock關鍵字定義如下:

private static readonly object Form_Lock = new object();//鎖對象的標準寫法

修改代碼如下:

int syncNum = 0;

int AsyncNum = 0;
for (int i = 0; i < 10000; i++)
{
    syncNum++;
}
Console.WriteLine($"syncNum={syncNum}");

for (int i = 0; i < 10000; i++)
{
    Task.Run(() =>
    {
        lock (Form_Lock)
        {
            AsyncNum++;
        }
    });
}
// 休眠5秒,等待所有線程都執(zhí)行完畢
Thread.Sleep(5000);
Console.WriteLine($"AsyncNum ={AsyncNum}");

程序運行結果:

除了使用lock,我們還可以使用數據分拆,避免多線程操作同一個數據,這樣又安全又高效。

到此這篇關于C#多線程相關操作的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

  • C#提取PDF表單數據的實現流程

    C#提取PDF表單數據的實現流程

    PDF表單是一種常見的數據收集工具,廣泛應用于調查問卷、業(yè)務合同等場景,憑借出色的跨平臺兼容性和標準化特點,PDF表單在各行各業(yè)中得到了廣泛應用,本文將探討如何使用C# 實現自動化PDF表單數據提取流程,需要的朋友可以參考下
    2025-01-01
  • C# 腳本引擎RulesEngine的使用詳解

    C# 腳本引擎RulesEngine的使用詳解

    這篇文章主要介紹了C# 腳本引擎RulesEngine的使用方法,幫助大家更好的理解和使用c#,感興趣的朋友可以了解下
    2021-02-02
  • C#設計模式之外觀模式介紹

    C#設計模式之外觀模式介紹

    外觀模式:為子系統(tǒng)中的一組接口提供一個一致的界面,此模式定義了一個高層的接口,這個借口使得這子系統(tǒng)容易使用
    2012-10-10
  • Unity UGUI實現滑動翻頁效果

    Unity UGUI實現滑動翻頁效果

    這篇文章主要為大家詳細介紹了Unity UGUI實現滑動翻頁效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-04-04
  • WinForm中comboBox控件數據綁定實現方法

    WinForm中comboBox控件數據綁定實現方法

    這篇文章主要介紹了WinForm中comboBox控件數據綁定實現方法,結合實例形式分析了WinForm實現comboBox控件數據綁定的常用方法與相關操作技巧,需要的朋友可以參考下
    2017-05-05
  • C# winfrom 模擬ftp文件管理實現代碼

    C# winfrom 模擬ftp文件管理實現代碼

    從網上找到的非常好用的模擬ftp管理代碼,整理了一下,希望對需要的人有幫助
    2014-01-01
  • c#刪除代碼中的單行注釋行示例

    c#刪除代碼中的單行注釋行示例

    本文提供了c#刪除代碼中的單行注釋行的示例,還可以看到文件流的使用方法,大家參考使用吧
    2014-01-01
  • C# WPF實現的語音播放自定義控件

    C# WPF實現的語音播放自定義控件

    這篇文章主要介紹了C# WPF實現的語音播放自定義控件,幫助大家更好的理解和學習使用c# WPF技術,感興趣的朋友可以了解下
    2021-04-04
  • C# winform編程中響應回車鍵的實現代碼

    C# winform編程中響應回車鍵的實現代碼

    這篇文章主要介紹了C# winform編程中響應回車鍵的實現代碼,既在窗口上響應回車鍵事件的方法,需要的朋友可以參考下
    2014-08-08
  • c#二維碼生成的代碼分享

    c#二維碼生成的代碼分享

    c#生成二維碼實現示例代碼分享,生成方法是調用外網API,為了不直接調用別人的接口,創(chuàng)建一個QrImg.aspx用于顯示二維碼,傳參數即可
    2013-12-12

最新評論

射洪县| 彭州市| 井研县| 阳新县| 息烽县| 瓮安县| 美姑县| 凤城市| 榆社县| 汉源县| 江陵县| 阳原县| 建阳市| 集贤县| 麻栗坡县| 景德镇市| 通江县| 穆棱市| 永川市| 淮南市| 通渭县| 垦利县| 米林县| 汉中市| 邯郸市| 丰顺县| 含山县| 通榆县| 安国市| 青浦区| 靖江市| 通河县| 宝丰县| 辽中县| 富宁县| 德州市| 鹤壁市| 临泽县| 谢通门县| 古丈县| 勃利县|