欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C#多線程系列之任務(wù)基礎(chǔ)(一)

 更新時間:2022年02月14日 10:08:12   作者:癡者工良  
本文詳細(xì)講解了C#多線程的任務(wù)基礎(chǔ),文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下

多線程編程

多線程編程模式

.NET 中,有三種異步編程模式,分別是基于任務(wù)的異步模式(TAP)、基于事件的異步模式(EAP)、異步編程模式(APM)。

  • 基于任務(wù)的異步模式 (TAP) :.NET 推薦使用的異步編程方法,該模式使用單一方法表示異步操作的開始和完成。包括我們常用的 async 、await 關(guān)鍵字,屬于該模式的支持。
  • 基于事件的異步模式 (EAP) :是提供異步行為的基于事件的舊模型。《C#多線程(12):線程池》中提到過此模式,.NET Core 已經(jīng)不支持。
  • 異步編程模型 (APM) 模式:也稱為 IAsyncResult 模式,,這是使用 IAsyncResult 接口提供異步行為的舊模型。.NET Core 也不支持,請參考 《C#多線程(12):線程池》。

前面,我們學(xué)習(xí)了三部分的內(nèi)容:

  • 線程基礎(chǔ):如何創(chuàng)建線程、獲取線程信息以及等待線程完成任務(wù);
  • 線程同步:探究各種方式實現(xiàn)進(jìn)程和線程同步,以及線程等待;
  • 線程池:線程池的優(yōu)點和使用方法,基于任務(wù)的操作;

這篇開始探究任務(wù)和異步,而任務(wù)和異步是十分復(fù)雜的,內(nèi)容錯綜復(fù)雜,筆者可能講不好。。。

探究優(yōu)點

我們現(xiàn)在來探究一下多線程編程的復(fù)雜性。

  • 傳遞數(shù)據(jù)和返回結(jié)果

傳遞數(shù)據(jù)倒是沒啥問題,只是難以獲取到線程的返回值,處理線程的異常也需要技巧。

  • 監(jiān)控線程的狀態(tài)

新建新的線程后,如果需要確定新線程在何時完成,需要自旋或阻塞等方式等待。

  • 線程安全

設(shè)計時要考慮如果避免死鎖、合理使用各種同步鎖,要考慮原子操作,同步信號的處理需要技巧。

  • 性能

玩多線程,最大需求就是提升性能,但是多線程中有很多坑,使用不當(dāng)反而影響性能。

[以上總結(jié)可參考《C# 7.0本質(zhì)論》19.3節(jié),《C# 7.0核心技術(shù)指南》14.3 節(jié)]

我們通過使用線程池,可以解決上面的部分問題,但是還有更加好的選擇,就是 Task(任務(wù))。另外 Task 也是異步編程的基礎(chǔ)類型,后面很多內(nèi)容要圍繞 Task 展開。

原理的東西,還是多參考微軟官方文檔和書籍,筆者講得不一定準(zhǔn)確,而且不會深入說明這些。

任務(wù)操作

任務(wù)(Task)實在太多 API 了,也有各種騷操作,要講清楚實在不容易,我們要慢慢來,一點點進(jìn)步,一點點深入,多寫代碼測試。

下面與筆者一起,一步步熟悉、摸索 Task 的 API。

兩種創(chuàng)建任務(wù)的方式

通過其構(gòu)造函數(shù)創(chuàng)建一個任務(wù),其構(gòu)造函數(shù)定義為:

public Task (Action action);

其示例如下:

    class Program
    {
        static void Main()
        {
            // 定義兩個任務(wù)
            Task task1 = new Task(()=> 
            {
                Console.WriteLine("① 開始執(zhí)行");
                Thread.Sleep(TimeSpan.FromSeconds(1));

                Console.WriteLine("① 執(zhí)行中");
                Thread.Sleep(TimeSpan.FromSeconds(1));

                Console.WriteLine("① 執(zhí)行即將結(jié)束");
            });

            Task task2 = new Task(MyTask);
            // 開始任務(wù)
            task1.Start();
            task2.Start();

            Console.ReadKey();
        }

        private static void MyTask()
        {
            Console.WriteLine("② 開始執(zhí)行");
            Thread.Sleep(TimeSpan.FromSeconds(1));

            Console.WriteLine("② 執(zhí)行中");
            Thread.Sleep(TimeSpan.FromSeconds(1));

            Console.WriteLine("② 執(zhí)行即將結(jié)束");
        }
    }

.Start() 方法用于啟動一個任務(wù)。微軟文檔解釋:啟動 Task,并將它安排到當(dāng)前的 TaskScheduler 中執(zhí)行。

TaskScheduler 這個東西,我們后面講,別急。

另一種方式則使用 Task.Factory,此屬性用于創(chuàng)建和配置 Task 和 Task<TResult> 實例的工廠方法。

使用https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.taskfactory.startnew?view=netcore-3.1#--可以添加任務(wù)。

當(dāng)需要對長時間運行、計算限制的任務(wù)(計算密集型)進(jìn)行精細(xì)控制時才使用 StartNew() 方法;
官方推薦使用 Task.Run 方法啟動計算限制任務(wù)。 
Task.Factory.StartNew() 可以實現(xiàn)比 Task.Run() 更細(xì)粒度的控制。

Task.Factory.StartNew() 的重載方法是真的多,你可以參考: https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.taskfactory.startnew?view=netcore-3.1#--

這里我們使用兩個重載方法編寫示例:

public Task StartNew(Action action);
public Task StartNew(Action action, TaskCreationOptions creationOptions);

代碼示例如下:

    class Program
    {
        static void Main()
        {
            // 重載方法 1
            Task.Factory.StartNew(() =>
            {
                Console.WriteLine("① 開始執(zhí)行");
                Thread.Sleep(TimeSpan.FromSeconds(1));

                Console.WriteLine("① 執(zhí)行中");
                Thread.Sleep(TimeSpan.FromSeconds(1));

                Console.WriteLine("① 執(zhí)行即將結(jié)束");
            });

            // 重載方法 1
            Task.Factory.StartNew(MyTask);

            // 重載方法 2
            Task.Factory.StartNew(() =>
            {
                Console.WriteLine("① 開始執(zhí)行");
                Thread.Sleep(TimeSpan.FromSeconds(1));

                Console.WriteLine("① 執(zhí)行中");
                Thread.Sleep(TimeSpan.FromSeconds(1));

                Console.WriteLine("① 執(zhí)行即將結(jié)束");
            },TaskCreationOptions.LongRunning);

            Console.ReadKey();
        }

        // public delegate void TimerCallback(object? state);
        private static void MyTask()
        {
            Console.WriteLine("② 開始執(zhí)行");
            Thread.Sleep(TimeSpan.FromSeconds(1));

            Console.WriteLine("② 執(zhí)行中");
            Thread.Sleep(TimeSpan.FromSeconds(1));

            Console.WriteLine("② 執(zhí)行即將結(jié)束");
        }
    }

通過 Task.Factory.StartNew() 方法添加的任務(wù),會進(jìn)入線程池任務(wù)隊列然后自動執(zhí)行,不需要手動啟動。

TaskCreationOptions.LongRunning 是控制任務(wù)創(chuàng)建特性的枚舉,后面講。

Task.Run() 創(chuàng)建任務(wù)

Task.Run() 創(chuàng)建任務(wù),跟 Task.Factory.StartNew() 差不多,當(dāng)然 Task.Run() 還有很多重載方法和騷操作,我們后面再來學(xué)。

Task.Run() 創(chuàng)建任務(wù)示例代碼如下:

        static void Main()
        {
            Task.Run(() =>
            {
                Console.WriteLine("① 開始執(zhí)行");
                Thread.Sleep(TimeSpan.FromSeconds(1));

                Console.WriteLine("① 執(zhí)行中");
                Thread.Sleep(TimeSpan.FromSeconds(1));

                Console.WriteLine("① 執(zhí)行即將結(jié)束");
            });
            Console.ReadKey();
        }

取消任務(wù)

取消任務(wù),《C#多線程(12):線程池》 中說過一次,不過控制太自由,全靠任務(wù)本身自覺判斷是否取消。

這里我們通過 Task 來實現(xiàn)任務(wù)的取消,其取消是實時的、自動的,并且不需要手工控制。

其構(gòu)造函數(shù)如下:

public Task StartNew(Action action, CancellationToken cancellationToken);

代碼示例如下:

按下回車鍵的時候記得切換字母模式。

    class Program
    {
        static void Main()
        {
            Console.WriteLine("任務(wù)開始啟動,按下任意鍵,取消執(zhí)行任務(wù)");
            CancellationTokenSource cts = new CancellationTokenSource();
            Task.Factory.StartNew(MyTask, cts.Token);

            Console.ReadKey();

            cts.Cancel();       // 取消任務(wù)
            Console.ReadKey();
        }

        // public delegate void TimerCallback(object? state);
        private static void MyTask()
        {
            Console.WriteLine(" 開始執(zhí)行");
            int i = 0;
            while (true)
            {
                Console.WriteLine($" 第{i}次任務(wù)");
                Thread.Sleep(TimeSpan.FromSeconds(1));

                Console.WriteLine("     執(zhí)行中");
                Thread.Sleep(TimeSpan.FromSeconds(1));

                Console.WriteLine("     執(zhí)行結(jié)束");
                i++;
            }
        }
    }

父子任務(wù)

前面創(chuàng)建任務(wù)的時候,我們碰到了 TaskCreationOptions.LongRunning 這個枚舉類型,這個枚舉用于控制任務(wù)的創(chuàng)建以及設(shè)定任務(wù)的行為。

其枚舉如下:

枚舉說明
AttachedToParent4指定將任務(wù)附加到任務(wù)層次結(jié)構(gòu)中的某個父級。
DenyChildAttach8指定任何嘗試作為附加的子任務(wù)執(zhí)行的子任務(wù)都無法附加到父任務(wù),會改成作為分離的子任務(wù)執(zhí)行。
HideScheduler16防止環(huán)境計劃程序被視為已創(chuàng)建任務(wù)的當(dāng)前計劃程序。
LongRunning2指定任務(wù)將是長時間運行的、粗粒度的操作,涉及比細(xì)化的系統(tǒng)更少、更大的組件。
None0指定應(yīng)使用默認(rèn)行為。
PreferFairness1提示 TaskScheduler 以一種盡可能公平的方式安排任務(wù)。
RunContinuationsAsynchronously64強(qiáng)制異步執(zhí)行添加到當(dāng)前任務(wù)的延續(xù)任務(wù)。

這個枚舉在 TaskFactory 和 TaskFactory<TResult> 、Task 和 Task<TResult> 、

StartNew()、FromAsync() 、TaskCompletionSource<TResult> 等地方可以使用到。

子任務(wù)使用了 TaskCreationOptions.AttachedToParent ,并不是指父任務(wù)要等待子任務(wù)完成后,父任務(wù)才能繼續(xù)完往下執(zhí)行;而是指父任務(wù)如果先執(zhí)行完畢,那么必須等待子任務(wù)完成后,父任務(wù)才算完成。

這里來探究 TaskCreationOptions.AttachedToParent的使用。代碼示例如下:

            // 父子任務(wù)
            Task task = new Task(() =>
            {
                // TaskCreationOptions.AttachedToParent
                // 將此任務(wù)附加到父任務(wù)中
                // 父任務(wù)需要等待所有子任務(wù)完成后,才能算完成
                Task task1 = new Task(() =>
                {
                    Thread.Sleep(TimeSpan.FromSeconds(1));
                    for (int i = 0; i < 5; i++)
                    {
                        Console.WriteLine("     內(nèi)層任務(wù)1");
                        Thread.Sleep(TimeSpan.FromSeconds(0.5));
                    }
                }, TaskCreationOptions.AttachedToParent);
                task1.Start();

                Console.WriteLine("最外層任務(wù)");
                Thread.Sleep(TimeSpan.FromSeconds(1));
            });

            task.Start();
            task.Wait();

            Console.ReadKey();

而 TaskCreationOptions.DenyChildAttach 則不允許其它任務(wù)附加到外層任務(wù)中。

        static void Main()
        {
            // 不允許出現(xiàn)父子任務(wù)
            Task task = new Task(() =>
            {
                Task task1 = new Task(() =>
                {
                    Thread.Sleep(TimeSpan.FromSeconds(1));
                    for (int i = 0; i < 5; i++)
                    {
                        Console.WriteLine("  內(nèi)層任務(wù)1");
                        Thread.Sleep(TimeSpan.FromSeconds(0.5));
                    }
                }, TaskCreationOptions.AttachedToParent);
                task1.Start();

                Console.WriteLine("最外層任務(wù)");
                Thread.Sleep(TimeSpan.FromSeconds(1));
            }, TaskCreationOptions.DenyChildAttach); // 不收兒子

            task.Start();
            task.Wait();

            Console.ReadKey();
        }

然后,這里也學(xué)習(xí)了一個新的 Task 方法:Wait() 等待 Task 完成執(zhí)行過程。Wait() 也可以設(shè)置超時時間。

如果父任務(wù)是通過調(diào)用 Task.Run 方法而創(chuàng)建的,則可以隱式阻止子任務(wù)附加到其中。

關(guān)于附加的子任務(wù),請參考:https://docs.microsoft.com/zh-cn/dotnet/standard/parallel-programming/attached-and-detached-child-tasks?view=netcore-3.1

任務(wù)返回結(jié)果以及異步獲取返回結(jié)果

要獲取任務(wù)返回結(jié)果,要使用泛型類或方法創(chuàng)建任務(wù),例如 Task<Tresult>、Task.Factory.StartNew<TResult>()、Task.Run<TResult>。

通過 其泛型的 的 Result 屬性,可以獲得返回結(jié)果。

異步獲取任務(wù)執(zhí)行結(jié)果:

    class Program
    {
        static void Main()
        {
            // *******************************
            Task<int> task = new Task<int>(() =>
            {
                return 666;
            });
            // 執(zhí)行
            task.Start();
            // 獲取結(jié)果,屬于異步
            int number = task.Result;

            // *******************************
            task = Task.Factory.StartNew<int>(() =>
            {
                return 666;
            });

            // 也可以異步獲取結(jié)果
            number = task.Result;

            // *******************************
            task = Task.Run<int>(() =>
              {
                  return 666;
              });

            // 也可以異步獲取結(jié)果
            number = task.Result;
            Console.ReadKey();
        }
    }

如果要同步的話,可以改成:

            int number = Task.Factory.StartNew<int>(() =>
            {
                return 666;
            }).Result;

捕獲任務(wù)異常

進(jìn)行中的任務(wù)發(fā)生了異常,不會直接拋出來阻止主線程執(zhí)行,當(dāng)獲取任務(wù)處理結(jié)果或者等待任務(wù)完成時,異常會重新拋出。

示例如下:

        static void Main()
        {
            // *******************************
            Task<int> task = new Task<int>(() =>
            {
                throw new Exception("反正就想彈出一個異常");
            });
            // 執(zhí)行
            task.Start();
            Console.WriteLine("任務(wù)中的異常不會直接傳播到主線程");
            Thread.Sleep(TimeSpan.FromSeconds(1));

            // 當(dāng)任務(wù)發(fā)生異常,獲取結(jié)果時會彈出
            int number = task.Result;

            // task.Wait(); 等待任務(wù)時,如果發(fā)生異常,也會彈出

            Console.ReadKey();
        }

亂拋出異常不是很好的行為噢~可以改成如下:

        static void Main()
        {
            Task<Program> task = new Task<Program>(() =>
            {
                try
                {
                    throw new Exception("反正就想彈出一個異常");
                    return new Program();
                }
                catch
                {
                    return null;
                }
            });
            task.Start();

            var result = task.Result;
            if (result is null)
                Console.WriteLine("任務(wù)執(zhí)行失敗");
            else Console.WriteLine("任務(wù)執(zhí)行成功");

            Console.ReadKey();
        }

全局捕獲任務(wù)異常

TaskScheduler.UnobservedTaskException 是一個事件,其委托定義如下:

public delegate void EventHandler<TEventArgs>(object? sender, TEventArgs e);

下面是一個示例:

請發(fā)布程序后,打開目錄執(zhí)行程序。

    class Program
    {
        static void Main()
        {
            TaskScheduler.UnobservedTaskException += MyTaskException;

            Task.Factory.StartNew(() =>
             {
                 throw new ArgumentNullException();
             });
            Thread.Sleep(100);
            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.WriteLine("Done");
            Console.ReadKey();
        }
        public static void MyTaskException(object sender, UnobservedTaskExceptionEventArgs eventArgs)
        {
            // eventArgs.SetObserved();
            ((AggregateException)eventArgs.Exception).Handle(ex =>
            {
                Console.WriteLine("Exception type: {0}", ex.GetType());
                return true;
            });
        }
    }

TaskScheduler.UnobservedTaskException 到底怎么用,筆者不太清楚。而且效果難以觀察。

請參考:

https://stackoverflow.com/search?q=TaskScheduler.UnobservedTaskException

到此這篇關(guān)于C#多線程系列之任務(wù)基礎(chǔ)(一)的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • C#字節(jié)數(shù)組(byte[])和字符串相互轉(zhuǎn)換方式

    C#字節(jié)數(shù)組(byte[])和字符串相互轉(zhuǎn)換方式

    這篇文章主要介紹了C#字節(jié)數(shù)組(byte[])和字符串相互轉(zhuǎn)換方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • C#繪制中國國旗的方法

    C#繪制中國國旗的方法

    這篇文章主要介紹了C#繪制中國國旗的方法,以實例形式較為詳細(xì)的分析了C#圖形繪制的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-08-08
  • C# 多進(jìn)程打開PPT的示例教程

    C# 多進(jìn)程打開PPT的示例教程

    這篇文章主要介紹了C# 多進(jìn)程打開PPT的示例教程,幫助大家更好的理解和使用c#,感興趣的朋友可以了解下
    2021-01-01
  • c# 動態(tài)加載dll文件,并實現(xiàn)調(diào)用其中的簡單方法

    c# 動態(tài)加載dll文件,并實現(xiàn)調(diào)用其中的簡單方法

    下面小編就為大家?guī)硪黄猚# 動態(tài)加載dll文件,并實現(xiàn)調(diào)用其中的簡單方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-01-01
  • C#深淺拷貝的深入解析

    C#深淺拷貝的深入解析

    這篇文章主要給大家介紹了關(guān)于C#深淺拷貝的深入解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者使用C#具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • C#?Unity使用正則表達(dá)式去除部分富文本的代碼示例

    C#?Unity使用正則表達(dá)式去除部分富文本的代碼示例

    正則表達(dá)式在我們?nèi)粘i_發(fā)中的用處不用多說了吧,下面這篇文章主要給大家介紹了關(guān)于C#?Unity使用正則表達(dá)式去除部分富文本的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-03-03
  • 聚星C#數(shù)字信號處理工具包頻譜分析的用法

    聚星C#數(shù)字信號處理工具包頻譜分析的用法

    這篇文章主要介紹了聚星C#數(shù)字信號處理工具包頻譜分析的用法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • C# 解決在Dictionary中使用枚舉的效率問題

    C# 解決在Dictionary中使用枚舉的效率問題

    這篇文章主要介紹了C# 解決在Dictionary中使用枚舉的效率問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • C# 使用Serilog日志框架的方法

    C# 使用Serilog日志框架的方法

    Serilog是一款配置方便,使用靈活的日志框架,這篇文章主要介紹了C# 使用Serilog日志框架,需要的朋友可以參考下
    2023-11-11
  • C#手工雙緩沖技術(shù)用法實例分析

    C#手工雙緩沖技術(shù)用法實例分析

    這篇文章主要介紹了C#手工雙緩沖技術(shù)用法,實例分析了手工雙緩沖技術(shù)的實現(xiàn)技巧,需要的朋友可以參考下
    2015-06-06

最新評論