中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

C#異步多線程使用中的常見問題有哪些

發布時間:2022-01-05 13:29:46 來源:億速云 閱讀:108 作者:iii 欄目:開發技術

本篇內容主要講解“C#異步多線程使用中的常見問題有哪些”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“C#異步多線程使用中的常見問題有哪些”吧!

異常處理

小伙伴有沒有想過,多線程的異常怎么處理,同步方法內的異常處理,想必都非常非常熟悉了。那多線程是什么樣的呢,接著我講解多線程的異常處理

首先,我們定義個任務列表,當 11、12 次的時候,拋出一個異常,最外圍使用 try catch 包一下

static void Main(string[] args)
{
    Console.WriteLine($"Main Start,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

    try
    {
        TaskFactory taskFactory = new TaskFactory();
        List<Task> tasks = new List<Task>();
        for (int i = 0; i < 20; i++)
        {
            string name = $"第 {i} 次";

            Action<object> action = t =>
            {
                Thread.Sleep(2 * 1000);
                if (name.ToString().Equals("第 11 次"))
                {
                    throw new Exception($"{t},執行失敗");
                }
                if (name.ToString().Equals("第 12 次"))
                {
                    throw new Exception($"{t},執行失敗");
                }
                Console.WriteLine($"{t},執行成功");
            };

            tasks.Add(taskFactory.StartNew(action, name));
        }
    }
    catch (AggregateException aex)
    {
        foreach (var item in aex.InnerExceptions)
        {
            Console.WriteLine("Main AggregateException:" + item.Message);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("Main Exception:" + ex.Message);
    }

    Console.WriteLine($"Main End,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

    Console.ReadLine();
}

啟動程序,可以看到 vs 捕獲到了異常的代碼行,但 catch 并未捕獲到異常,這是為什么呢?是因為線程里面的異常被吞掉了,從運行的結果也可以看到,main end 在子線程沒有執行任時就已經結束了,那說明 catch 已經執行過去了。

C#異步多線程使用中的常見問題有哪些

那有沒有辦法捕獲多線程的異常呢?答案:有的,等待線程完成計算即可

看下面代碼,有個特殊的地方 AggregateException.InnerExceptions 專門為多線程準備的,可以查看多線程異常信息

static void Main(string[] args)
{
    Console.WriteLine($"Main Start,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

    try
    {
        TaskFactory taskFactory = new TaskFactory();
        List<Task> tasks = new List<Task>();
        for (int i = 0; i < 20; i++)
        {
            string name = $"第 {i} 次";

            Action<object> action = t =>
            {
                Thread.Sleep(2 * 1000);
                if (name.ToString().Equals("第 11 次"))
                {
                    throw new Exception($"{t},執行失敗");
                }
                if (name.ToString().Equals("第 12 次"))
                {
                    throw new Exception($"{t},執行失敗");
                }
                Console.WriteLine($"{t},執行成功");
            };

            tasks.Add(taskFactory.StartNew(action, name));
        }

        Task.WaitAll(tasks.ToArray());
    }
    catch (AggregateException aex)
    {
        foreach (var item in aex.InnerExceptions)
        {
            Console.WriteLine("Main AggregateException:" + item.Message);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("Main Exception:" + ex.Message);
    }

    Console.WriteLine($"Main End,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

    Console.ReadLine();
}

啟動線程,可以看到任務全部執行完畢,且 AggregateException.InnerExceptions 存儲了,子線程執行時的異常信息

C#異步多線程使用中的常見問題有哪些

但 WaitAll 不好,總不能一直 WaitAll 吧,它會卡界面。并不適用于異步場景對吧,接著來看另外一直解決方案。就是子線程里不允許出現異常,如果有自己處理好,即 try catch 包一下,平時工作中建議這么做。

使用 try catch 將子線程執行的代碼包一下,且在 catch 打印錯誤信息

static void Main(string[] args)
{
    Console.WriteLine($"Main Start,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

    try
    {
        TaskFactory taskFactory = new TaskFactory();
        List<Task> tasks = new List<Task>();
        for (int i = 0; i < 20; i++)
        {
            string name = $"第 {i} 次";

            Action<object> action = t =>
            {
                try
                {
                    Thread.Sleep(2 * 1000);
                    if (name.ToString().Equals("第 11 次"))
                    {
                        throw new Exception($"{t},執行失敗");
                    }
                    if (name.ToString().Equals("第 12 次"))
                    {
                        throw new Exception($"{t},執行失敗");
                    }
                    Console.WriteLine($"{t},執行成功");
                }
                catch (Exception ex)
                {
					Console.WriteLine(ex.Message);
                }
            };

            tasks.Add(taskFactory.StartNew(action, name));
        }
    }
    catch (AggregateException aex)
    {
        foreach (var item in aex.InnerExceptions)
        {
            Console.WriteLine("Main AggregateException:" + item.Message);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("Main Exception:" + ex.Message);
    }

    Console.WriteLine($"Main End,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

    Console.ReadLine();
}

啟動程序,可以看到任務全部執行,且子線程異常也捕獲到

C#異步多線程使用中的常見問題有哪些

線程取消

有時候會有這樣的場景,多個任務并發執行,如果某個任務失敗了,通知其他的任務都停下來。首先打個預防針 Task 在外部無法中止的,Thread.Abort 不靠譜。其實線程取消的這個想法是錯誤的,線程是 OS 的資源,程序是無法掌控什么時候取消,發出一個動作可能立馬取消,也可能等 1 s 取消。

解決方案:線程自己停止自己,定義公共的變量,修改變量狀態,其他線程不斷檢測公共變量

例如:CancellationTokenSource 就是公共變量,初始化為 false 狀態,程序執行 CancellationTokenSource .Cancel() 方法會取消,其他線程檢測到 CancellationTokenSource .IsCancellationRequested 會是取消狀態。CancellationTokenSource.Token 在啟動 Task 時傳入,如果已經 CancellationTokenSource.Cancel() ,這個任務會放棄啟動,拋出一個異常的形式放棄。

static void Main(string[] args)
{
    Console.WriteLine($"Main Start,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

    try
    {
        TaskFactory taskFactory = new TaskFactory();
        List<Task> tasks = new List<Task>();
        CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); // bool 

        for (int i = 0; i < 20; i++)
        {
            string name = $"第 {i} 次";

            Action<object> action = t =>
            {
                try
                {
                    Thread.Sleep(2 * 1000);
                    if (name.ToString().Equals("第 11 次"))
                    {
                        throw new Exception($"{t},執行失敗");
                    }
                    if (name.ToString().Equals("第 12 次"))
                    {
                        throw new Exception($"{t},執行失敗");
                    }
                    if (cancellationTokenSource.IsCancellationRequested) // 檢測信號量
                    {
                        Console.WriteLine($"{t},放棄執行");
                        return;
                    }
                    Console.WriteLine($"{t},執行成功");
                }
                catch (Exception ex)
                {
                    cancellationTokenSource.Cancel();
                    Console.WriteLine(ex.Message);
                }
            };

            tasks.Add(taskFactory.StartNew(action, name,cancellationTokenSource.Token));
        }

        Task.WaitAll(tasks.ToArray());
    }
    catch (AggregateException aex)
    {
        foreach (var item in aex.InnerExceptions)
        {
            Console.WriteLine("Main AggregateException:" + item.Message);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("Main Exception:" + ex.Message);
    }

    Console.WriteLine($"Main End,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

    Console.ReadLine();
}

啟動程序,可以看到 11、12 此任務失敗,18、19 放棄了任務執。有的小伙伴疑問了,12 之后的部分為什么執行成功了,因為 CPU 是分時分片的嗎,會有延遲,延遲少不了。

C#異步多線程使用中的常見問題有哪些

臨時變量

首先看個代碼,循環 5 次,多線程的方式,依次輸出序號

static void Main(string[] args)
{
    Console.WriteLine($"Main Start,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

    for (int i = 0; i < 5; i++)
    {
        Task.Run(() => {
            Console.WriteLine(i);
        });
    }

    Console.WriteLine($"Main End,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

    Console.ReadLine();
}

啟動程序,不是我們預期的結果 0、1、2、3、4,為什么是 5 個 5 呢?因為全程只有一個 i ,當主線程執行完畢時 i = 5 ,但子線程可能還沒有開始執行任務,輪到子線程取 i 時,已經是主線程 1 循環完畢后的 5 了。

C#異步多線程使用中的常見問題有哪些

改造代碼:在 for 循環內加一行代碼 int k = i,且在子線程用的變量也改為 k

static void Main(string[] args)
{
    Console.WriteLine($"Main Start,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

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

    Console.WriteLine($"Main End,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

    Console.ReadLine();
}

啟動程序,可以看到是我們預期的結果 0、1、2、3、4,為什么會這樣子呢?因為全程有 5 個 k,每次循環都會創建一個 k 存儲當前的 i,不同的子線程使用的也是,每次循環的 i 值。

C#異步多線程使用中的常見問題有哪些

線程安全

首先為什么會有線程安全的概念呢?首先我們來看一個正常程序,如下

static void Main(string[] args)
{
    Console.WriteLine($"Main Start,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

    int TotalCount = 0;
    List<int> vs = new List<int>();

    for (int i = 0; i < 10000; i++)
    {
        TotalCount += 1;
        vs.Add(i);
    }

    Console.WriteLine(TotalCount);
    Console.WriteLine(vs.Count);

    Console.WriteLine($"Main End,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

    Console.ReadLine();
}

啟動程序,可以看到循環 10000 次,最終的求和與列表里的數據量都是 10000,這是正常的

C#異步多線程使用中的常見問題有哪些

接著,將求和與添加列表,換成多線程,等待全部線程完成工作后,打印信息

static void Main(string[] args)
{
    Console.WriteLine($"Main Start,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

    int TotalCount = 0;
    List<int> vs = new List<int>();

    TaskFactory taskFactory = new TaskFactory();
    List<Task> tasks = new List<Task>();

    for (int i = 0; i < 10000; i++)
    {
        int k = i;
        tasks.Add(taskFactory.StartNew(() =>
        {
            TotalCount += 1;
            vs.Add(i);
        }));
    }

    Task.WaitAll(tasks.ToArray());

    Console.WriteLine(TotalCount);
    Console.WriteLine(vs.Count);

    Console.WriteLine($"Main End,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

    Console.ReadLine();
}

啟動程序,可以看到,兩個結果都不是 10000 呢?這就是線程安全

因為 TotalCount 是個共享的變量,當多個線程去取 TotalCount 進行 +1 后,線程都去放值的時候,后一個線程會替換掉前一個線程放置的值,所以就會形成做最終不是 10000 的結果。列表,可以看做是一個連續的塊,當多線程添加的時候,也會進行覆蓋。

C#異步多線程使用中的常見問題有哪些

如何解決呢?答案:lock、安全隊列、拆分合并計算。下面對 lock 進行講解,安全隊列與拆分合并計算,有興趣的小伙伴可以私下交流

1 .lock
第一種,通過加鎖的方式,這種也是日常工作總常用的一種。首先定義個私有的靜態引用類型的變量,然后將需要鎖的運算放到 lock () 方法內

在 { } 內同一時刻,只有一個線程執行,所以盡可能 {} 放置必要的邏輯運行提高效率。lock 只能鎖引用類型,原理是占用這個引用鏈接。不要用 string 會享元,即如 lock() 是相同的字符串,無論定義多少個變量,其實都是一個。

internal class Program
{
    private static readonly object _lock = new object();

    static void Main(string[] args)
    {
        Console.WriteLine($"Main Start,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

        int TotalCount = 0;
        List<int> vs = new List<int>();

        TaskFactory taskFactory = new TaskFactory();
        List<Task> tasks = new List<Task>();

        for (int i = 0; i < 10000; i++)
        {
            int k = i;
            tasks.Add(taskFactory.StartNew(() =>
            {
                lock (_lock)
                {
                    TotalCount += 1;
                    vs.Add(i);
                }
            }));
        }

        Task.WaitAll(tasks.ToArray());

        Console.WriteLine(TotalCount);
        Console.WriteLine(vs.Count);

        Console.WriteLine($"Main End,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

        Console.ReadLine();
    }
}

啟動程序,可以看到,此時在多線程的情況下,最終的結果是正常的

C#異步多線程使用中的常見問題有哪些

這段代碼,是官方推薦寫法 private 防止外面也被引用,static 保證全場唯一

private static readonly object _lock = new object();

擴展:與 lock 等價的有個 Monitor,用法如下

private static object _lock = new object();

static void Main(string[] args)
{
    Console.WriteLine($"Main Start,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

    int TotalCount = 0;
    List<int> vs = new List<int>();

    TaskFactory taskFactory = new TaskFactory();
    List<Task> tasks = new List<Task>();

    for (int i = 0; i < 10000; i++)
    {
        int k = i;
        tasks.Add(taskFactory.StartNew(() =>
        {
            Monitor.Enter(_lock);
            TotalCount += 1;
            vs.Add(i);
            Monitor.Exit(_lock);
        }));
    }

    Task.WaitAll(tasks.ToArray());

    Console.WriteLine(TotalCount);
    Console.WriteLine(vs.Count);

    Console.WriteLine($"Main End,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

    Console.ReadLine();
}

C#異步多線程使用中的常見問題有哪些

到此,相信大家對“C#異步多線程使用中的常見問題有哪些”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

龙川县| 普安县| 武汉市| 三原县| 阿瓦提县| 潞城市| 东丰县| 勃利县| 扶余县| 股票| 濮阳市| 绥棱县| 万州区| 洞头县| 营山县| 盐池县| 定兴县| 巴彦淖尔市| 贵州省| 桃园县| 仙居县| 许昌县| 曲松县| 靖州| 黄陵县| 汉阴县| 峨眉山市| 凤冈县| 泰安市| 淮安市| 柏乡县| 尼木县| 龙泉市| 柯坪县| 那坡县| 潜山县| 历史| 铁岭县| 镇原县| 苍南县| 正安县|