淺說C#非同步和同步

来源:https://www.cnblogs.com/paulcode/archive/2018/03/01/8489304.html
-Advertisement-
Play Games

提到非同步,那麼與之對應的是什麼呢?同步。那麼C#的非同步和同步是如何工作的呢? 首先,我們先來看看慄子: 新建一個控制台應用程式,在Program文件中添加如下代碼: 這個慄子很簡單,定義了兩個方法:TaskOne,TaskTwo。在裡面每隔一秒輸出一次當前時間,和當前線程。TaskOne迴圈5次和T ...


提到非同步,那麼與之對應的是什麼呢?同步。那麼C#的非同步和同步是如何工作的呢?

首先,我們先來看看慄子:

新建一個控制台應用程式,在Program文件中添加如下代碼:

 1  static void Main(string[] args)
 2         {
 3             //計時器
 4             Stopwatch watch = new Stopwatch();
 5             //開始計時
 6             watch.Start();
 7             Console.WriteLine($"{DateTime.Now.ToString()} 進入Main方法,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
 8             //調用任務一(同步)
 9             TaskOne();
10             // 調用任務二
11             TaskTwo();
12             //停止計時
13             watch.Stop();
14             Console.WriteLine($"{DateTime.Now.ToString()} 退出Main方法,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
15             Console.WriteLine($"主線程總耗時:{watch.ElapsedMilliseconds}ms");
16             Console.ReadKey();
17         }
18         
19         /// <summary>
20         /// 任務一
21         /// </summary>
22         static void TaskOne()
23         {
24             Console.WriteLine($"{DateTime.Now.ToString()} 進入TaskOne方法,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
25             for (int i = 0; i < 5; i++)
26             {
27                 Console.WriteLine($"{DateTime.Now.ToString()} TaskOne正在執行,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
28                 System.Threading.Thread.Sleep(1000);
29             }
30             Console.WriteLine($"{DateTime.Now.ToString()} 退出TaskOne方法,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
31         }
32         /// <summary>
33         /// 任務二
34         /// </summary>
35         static void TaskTwo(){
36             Console.WriteLine($"{DateTime.Now.ToString()} 進入TaskTwo方法,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
37             for (int i = 0; i < 2; i++)
38             {
39                 Console.WriteLine($"{DateTime.Now.ToString()} TaskTwo正在執行,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
40                 System.Threading.Thread.Sleep(1000);
41             }
42             Console.WriteLine($"{DateTime.Now.ToString()} 退出TaskTwo方法,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
43         }

這個慄子很簡單,定義了兩個方法:TaskOne,TaskTwo。在裡面每隔一秒輸出一次當前時間,和當前線程。TaskOne迴圈5次和TaskOne2次。然後在MAIN函數裡面順序調用,並記錄MAIN函數執行的總耗時時間。F5運行效果如圖:

從圖中可以看出,程式順序執行TaskOne之後,再執行TaskTwo。執行線程未改變。

下麵我們改改代碼,用非同步方式改寫下TaskOne。提到非同步,大家腦海裡隨之浮現的我想會是它吧。關鍵字async。當然與之成對出現的await也不能少了。先看看改寫後的代碼:

 /// <summary>
        /// 任務一(非同步)
        /// </summary>
        /// <returns></returns>
        static async Task<int> TaskOneAsync()
        {
            Console.WriteLine($"{DateTime.Now.ToString()} 進入TaskOneAsync方法,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
            var t = Task<int>.Run(() =>
            {
                var total = 0;
                for (int i = 0; i < 5; i++)
                {
                    total++;
                    Console.WriteLine($"{DateTime.Now.ToString()} TaskOneAsync正在執行,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
                    System.Threading.Thread.Sleep(1000);
                }
                return total;
            });
            Console.WriteLine($"{DateTime.Now.ToString()} 退出TaskOneAsync方法,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
            return await t;
        }

main函數改為調用非同步方法

 static void Main(string[] args)
        {
            //計時器
            Stopwatch watch = new Stopwatch();
            //開始計時
            watch.Start();
            Console.WriteLine($"{DateTime.Now.ToString()} 進入Main方法,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
            //調用任務一(同步)
            //TaskOne();
            //調用任務一(非同步)
            TaskOneAsync();
            // 調用任務二
            TaskTwo();
            //停止計時
            watch.Stop();
            Console.WriteLine($"{DateTime.Now.ToString()} 退出Main方法,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
            Console.WriteLine($"主線程總耗時:{watch.ElapsedMilliseconds}ms");
            Console.ReadKey();
        }

F5運行後效果:

我們可以看到Main函數的執行時間從7082ms變為了2404ms。時間大大的縮短了。但是,在main已經結束後,TaskOneAsync依然還在運行中....,並且TaskOneAsync的執行線程不是主線程9而是10!!

下麵我們來好好梳理下程式的執行過程,看圖便知:

 

可以看到當程式進入Main方法執行,進入TaskOneAsync後,線程ID依然是9,當遇到Task執行任務創建,並運行。主線程並沒有阻塞,而是單獨開了一個新的線程10去執行TASK任務。主線程依然順序執行,然後退出非同步方法。進入到TaskTwo中執行完畢,最後直到Main方法結束時,由於TaskOneAsync耗時較長,線程10依然繼續在執行Task。直到Task結束。其實系統,在Task任務Run的時候,已經新開了一個線程執行Task裡面的任務,然後主線程繼續執行TaskTwo,在TaskTwo執行這段期間,任務TaskOneAsync也在另一個線程同時j執行。可見,Task會新開一個線程執行命令,當前線程不會被阻塞,因此Main線程其實根本沒有像同步方式一樣執行最耗時的TaskOneAsync裡面的Task,而是交給了另外一個線程執行,這就是主線程執行時間,大大縮短的原因。因此,這種處理機制,對於用戶體驗,是比較友好的。其實,在我們開發中,常常見到以async結尾的方法。最常見的應該是IO讀取,寫入,以及 http資源請求相關類庫方法。因為這些都是比較耗時的,一般耗時的工作,為了不影響主線程響應,我們一般都採用非同步方式進行處理。

那麼,當我們主線程,需要獲取Task任務返回結果時,主線程會阻塞線程等待其結果返回後,再繼續執行下去。改下Main方法裡面的代碼,驗證一下:

 如圖,得以驗證,主線程阻塞了線程,等待Task執行完畢後,再繼續執行。

歸納總結,非同步和同步,我是這樣理解的:

同步:一段代碼指令,在同一線程上,被順序執行,中間沒有插隊。就好比去電影院買票,有很多人(待執行的指令),但是只有一個視窗(一個線程,一般指主線程)。後面的人,只能等前面的人買了票,走了,才能前一步,他們的步調是一致。所以,稱之為同步。

非同步:一段代碼指令,在執行的時候,其中一些指令與指令之間,被執行的時間點一樣,但是操作其執行的線程不一樣。兩者存在一段時間的並行現象。好比電影院看到排隊買票的人越來越多,經理馬上又新安排了一個售票員開了一個新視窗(開新線程)售票,把原來排隊的人(待執行的指令),轉移了一部分到新的視窗繼續排隊買票。這樣原來售票視窗(主線程)的作業任務以及時間,則相應減少了。

非同步方式其實是一種處理機制,它有好處,也有弊端。如果我們無端的濫用,會起反作用。因為,新開線程會消耗線程資源。所以,秉承一個原則:在不影響主線程響應前提下,能不用則不用。

以上都是個人見解,如有錯誤,還望指出,望不吝賜教~~

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、Future Future和Callable基本是成對出現的,Callable負責產生結果,Future負責獲取結果。 1、Callable介面類似於Runnable,只是Runnable沒有返回值。 2、Callable任務除了返回正常結果之外,如果發生異常,該異常也會被返回,即Future可 ...
  • 一、使用場景 當一個java應用CPU的使用比較高或者到達100%以上的時候,需要分析代碼哪裡有問題。這時候可以使用jstack命令 二、怎麼使用 先使用命令ps –ef |grep keyword 找到應用的進程號,用PID表示。 然後使用命令導出當前的堆棧,命令如下 jstack 23000 > ...
  • mt19937 當你第一眼看到這玩意兒的時候 肯定禁不住吐槽:納尼?這是什麼鬼? 確實,這個東西鮮為人知,但是它卻有著卓越的性能 簡介 mt19937是c++11中加入的新特性 它是一種隨機數演算法,用法與rand()函數類似 但是具有速度快,周期長的特點(它的名字便來自周期長度:2^19937 1) ...
  • 一款有著強大的文檔轉換功能的工具,無論何時何地都會是現代辦公環境極為需要的。在本篇文章中,將繼續介紹關於Word文檔的轉換功能(Word轉XPS/SVG/EMF/EPUB/TIFF)希望方法中的代碼能為各位開發者們提供一定的參考價值。 PS:更多Word轉換功能可以參閱這兩篇文章 Word轉HTML ...
  • 這是一道面試題,首先finally{}裡面的code肯定是會執行的,至於在return前還是後, 看答案說的是在return後執行,我覺得不對,百度了一下,有說return前的,有說return後的,還有return中間執行的。遂做了一個小測試如下: 測試結果如下: 所以我覺得finally{}裡面 ...
  • 在.Net 4.0以後的版本,提供了一個類,該類在 System.Diagnostics命名空間下,使用該類就可以計算出執行結果相同的兩端代碼的效率,在代碼優化上是很實用的。 泛型效率是高是低呢??我們來測試下,代碼如下: 經過上述執行,我們可以把我們程式框架裡面的幫助類都改成泛型的。 ...
  • 一、問題描述: 資料庫中欄位 nvarchar類型 存放數據如下: '3.3×10' 二、解決方案: --測試用例CREATE TABLE #temp(NAME NVARCHAR(20) null) INSERT INTO #temp select FecalColiform from Water ...
  • 原文: [Announcing .NET Core 2.1 Preview 1](https://blogs.msdn.microsoft.com/dotnet/2018/02/27/announcing-net-core-2-1-preview-1/) 今天,我們宣佈發佈 .NET Core 2... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...