並行編程和任務(一)

来源:https://www.cnblogs.com/hulizhong/archive/2019/11/05/11798586.html
-Advertisement-
Play Games

前言 併發、並行。同步、非同步、互斥、多線程。我太難了。被這些詞搞懵了。前面我們在寫.Net基礎系列的時候寫過了關於.Net的非同步編程。那麼其他的都是些什麼東西呀。今天我們首先就來解決這個問題。把這些詞搞懂搞透。理清邏輯。然後最後我們進入並行編程的介紹。 概念初識 首先我們看併發和並行: 併發:併發指 ...


前言

  併發、並行。同步、非同步、互斥、多線程。我太難了。被這些詞搞懵了。前面我們在寫.Net基礎系列的時候寫過了關於.Net的非同步編程。那麼其他的都是些什麼東西呀。今天我們首先就來解決這個問題。把這些詞搞懂搞透。理清邏輯。然後最後我們進入並行編程的介紹。

概念初識

首先我們看併發和並行:

併發:併發指的是在操作系統中,一個是時間段內有多個程式在運行,但是呢。這幾個程式都運行在同一個處理機上,並且任意時間點都是一個程式運行在處理機上。

並行:並行指的是在操作系統中,一個時間段內有多個程式在運行,但是呢。這幾個程式分別運行在不同的處理機上。也就是說這些程式是一起運行的。

簡單理解也就是併發就像三個包子給一個人吃,一口吃一個包子。並行就是三個包子給三個人吃,三個人一口分別吃三個包子。

然後我們看看非同步與多線程概念:

剛剛我們講到併發的理解概念,其中併發包含兩種關係-同步和互斥。同步和互斥我們都是相對於臨界資源來談的。

互斥:進程間相互排斥使用臨界資源的現象就叫互斥。就好比進程A在訪問List集合的時候,進程B也想訪問,但是A在訪問。B就阻塞等待A訪問完成之後才去訪問。

同步:進程間的關係不是臨界資源的相互排斥,而是相互依賴。例如進程B需要讀取一個集合結果,但是這個集合結果需要進程A返回,當進程A沒有返回集合結果時,進程B就會因為沒有獲得信息而阻塞。當進程A返回信息。進程B就可以獲得信息被喚起繼續運行。

多線程:多線程可以說是程式設計的一個邏輯概念,多線程實現了線程的切換。使其看起來似乎是在同時運行多個線程一樣。是進程中併發運行的一段代碼。

非同步:非同步與同步相對應。同步是進程間相互依賴。非同步是進程間相互獨立。不需要等待上一個進程的結果。可以做自己的事情。

上面我們就介紹完了併發、並行、互斥、同步、多線程、非同步。我們總結下其中關聯吧:

非同步與多線程並不相等。非同步是需要達到的目的,多線程是一個是實現非同步的一種手段。最後達到的目的是什麼呢?就是併發中線程的切換。同步也可以實現線程切換,但是由於同步中IO等待會浪費時間,所以同步切換進程與非同步切換進行就有明顯的時間差距。

Parallel

今天我們介紹的是Parallel類。該類位於System.Threading.Tasks命名空間中。依次來實現數據和任務的並行性。

其中定義了並行的for和foreach的靜態方法、還包含著Parallel.Invoke()用於任務的並行性。我們下麵就來看看吧。

Parallel.For()

Parallel.For()方法類似於#中的for迴圈語句,但是Parallel.For()是可以並行運行的。不過並行運行並不保證迭代運行的順序。我們來看看。

 public static void ForEx()
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            List<Test> list = new List<Test>();
            for (int i = 0; i < 100; i++)
            {
                Test test = new Test();
                test.Name = "Name:" + i;
                test.Index = "Index:" + i;
                test.Time = DateTime.Now;
                list.Add(test);
                Task.Delay(10).Wait();
                Console.WriteLine("C#中的for迴圈:" + i);
            }
            stopwatch.Stop();
            Console.WriteLine("for  0-100 執行完成 耗時:{0}", stopwatch.ElapsedMilliseconds);

        }
        public static void ParallelFor()
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            List<Test> lists = new List<Test>();
            Parallel.For(1, 100, i =>
            {
                Test tests = new Test();
                tests.Name = "Name:" + i;
                tests.Index = "Index:" + i;
                tests.Time = DateTime.Now;
                lists.Add(tests);
                Task.Delay(10).Wait();
                Console.WriteLine("Parallel中的ParallelFor迴圈:" + i);
            });
            stopwatch.Stop();
            Console.WriteLine("ParallelFor  0-100 執行完成 耗時:{0}", stopwatch.ElapsedMilliseconds);
        }
        static void Main(string[] args)
        {
            Console.WriteLine("Start");
            ForEx();
            Console.WriteLine("for迴圈完成");
            ParallelFor();
            Console.WriteLine("End");
        }

 

這裡我們可以看到最後的運行結果圖使用for迴圈的執行下來都是依次執行。按照相應的順序。但是我們使用Parallel.For()的時候運行下來。也輸出了所有的結果,但是其順序就沒有保證了。

Parallel.ForEach()

我們再看Parallel.ForEach()提供了一個並行處理數據的機制。這裡類似於foreach語句,但是是以一部方式遍歷。這裡沒有確定遍歷的順序,其執行順序也就是不保證的。

  #region ForEach 語句比較 
        public static void ParallelForEach()
        {
            List<Test> result = new List<Test>();
            for (int i = 1; i < 100; i++)
            {
                Test model = new Test();
                model.Name = "Name" + i;
                model.Index = "Index" + i;
                model.Time = DateTime.Now;
                result.Add(model);
            }
            Parallel.ForEach<Test>(result, s => {
                Console.WriteLine(s.Name);
            });
        }
        #endregion     
static void Main(string[] args)
        {
            ParallelForEach();
        }

 

 

我們看這裡的運行結果,對數據集合進行了遍歷處理,但是其運行的順序不定,是亂序的結果。這也就是非同步遍歷的一個表現。

ParallelLoopState

下麵我們來看ParallelLoopState。它提供了兩個方法。一個是Break、一個是Stop。

Break:表示並行迴圈執行了當前迭代後應儘快停止執行。篩選出符合條件的執行,可能輸出完全。

Stop:表示並行迴圈應儘快停止執行。遇到符合條件後停止並行迴圈,可能不完全輸出。

下麵我們看看代碼:

  public static List<Test> GetListTest()
        {
            List<Test> result = new List<Test>();
            for (int i = 1; i < 100; i++)
            {
                Test model = new Test();
                model.Name = i.ToString();
                model.Index = "Index" + i;
                model.Time = DateTime.Now;
                result.Add(model);
            }
            return result;
        }
        public static void BraekFor()
        {
            var result = GetListTest();
            Parallel.For(0, result.Count, (int i, ParallelLoopState ls) =>
            {
                Task.Delay(10).Wait();
                if (i > 50)
                {
                    Console.WriteLine("Parallel.For使用Break停止當前迭代:" + i);
                    ls.Break();
                    return;
                }
                Console.WriteLine("測試Parallel.For的Break:" + i);
            });

        }

        public static void StopFor()
        {
            var result = GetListTest();
            Parallel.For(0, result.Count, (int i, ParallelLoopState ls) =>
            {
                Task.Delay(10).Wait();
                if (i > 50)
                {
                    Console.WriteLine("Parallel.For使用Stop停止 迭代:" + i);
                    ls.Stop();
                    return;
                }
                Console.WriteLine("測試Parallel.For的Stop:" + i);
            });
        }
        static void Main(string[] args)
        {
            BraekFor();
            StopFor();
        }

 

我們看對於Parallel.For()來說這個案例。使用Break()停止當前迭代會輸出符合條件所有結果,但是我們使用Stop的時候輸出部分的時候就停止了。

        public static void BraekForEach()
        {
            var result = GetListTest();
            Parallel.ForEach<Test>(result, (Test s, ParallelLoopState ls) =>
            {
                Task.Delay(10).Wait();
                if (Convert.ToInt32(s.Name) > 50)
                {
                    Console.WriteLine("Parallel.ForEach使用Break停止當前迭代:" + s.Name);
                    ls.Break();
                    return;
                }
                Console.WriteLine("測試Parallel.ForEach的Break:" + s.Name);
            });

        }

        public static void StopForEach()
        {
            var result = GetListTest();
            Parallel.ForEach<Test>(result, (Test s, ParallelLoopState ls) =>
            {
                Task.Delay(10).Wait();
                if (Convert.ToInt32(s.Name) > 50)
                {
                    Console.WriteLine("Parallel.ForEach使用Stop停止 迭代:" + s.Name);
                    ls.Stop();
                    return;
                }
                Console.WriteLine("測試Parallel.ForEach的Stop:" + s.Name);
            });
        }
        static void Main(string[] args)
        {
            BraekForEach();
            StopForEach();
        }

 

我們再對Parallel.ForEach進行測試,發現對於Stop和Break的用法和意義是一樣的。

Parallel.Invoke()

上面我們介紹了Parallel.For和Parallel.ForEach以及提供的兩個方法Break和Stop。上面介紹的這些都是對數據的並行處理執行。下麵我們介紹Parallel.Invoke()。它是針對於任務的並行運行處理。

這裡我們需要註意以下幾點:

1、如果我們傳入4個任務並行,那麼我們至少需要四個邏輯處理內核(硬體線程)才可能使四個任務一起運行。但是當其中一個內核繁忙,那麼底層的調度邏輯就可能會延遲某些方法的初始化執行。

2、Parallel.Invoke()所包含的並行任務不能相互依賴,因為運行執行的順序不可保證。

3、使用Parallel.Invoke()我們需要測試運行結果,觀察邏輯內核使用率以及實現加速。

4、使用Parallel.Invoke()會產生一些額外的開銷,例如分配硬體線程。

我們看下麵的案例:

下麵我們對一個集合的數據進行添加然後輸出。下麵我們分為四組測試。500條數據和1000條數據各兩個,分別是一般的同步任務和Parallel.Invoke()的並行任務執行。再觀察其運行的時間比較。

 

       #region Parallel.Invoke()使用共同資源

        public static List<Test> _tests = null;
        public static void TaskFive_One()
        {
            for (int i = 0; i < 500; i++)
            {
                Test test = new Test();
                test.Name = i.ToString();
                test.Index = i.ToString();
                test.Time = DateTime.Now;
                _tests.Add(test);
            }
            Console.WriteLine("TaskFive_One 500條數據第一個方法 執行完成");
        }
        public static void TaskFive_Two()
        {
            for (int i = 500; i < 1000; i++)
            {
                Test test = new Test();
                test.Name = i.ToString();
                test.Index = i.ToString();
                test.Time = DateTime.Now;
                _tests.Add(test);
            }
            Console.WriteLine("TaskFive_Two 500條數據第二個方法 執行完成");
        }
        public static void TaskFive_Three()
        {
            for (int i = 1000; i < 1500; i++)
            {
                Test test = new Test();
                test.Name = i.ToString();
                test.Index = i.ToString();
                test.Time = DateTime.Now;
                _tests.Add(test);
            }
            Console.WriteLine("TaskFive_Three 500條數據第三個方法 執行完成");
        }
        public static void TaskFive_Four()
        {
            for (int i = 1500; i < 2000; i++)
            {
                Test test = new Test();
                test.Name = i.ToString();
                test.Index = i.ToString();
                test.Time = DateTime.Now;
                _tests.Add(test);
            }
            Console.WriteLine("TaskFive_Four 500條數據第四個方法 執行完成");
        }



        public static void TaskOnethousand_One()
        {
            for (int i = 0; i < 1000; i++)
            {
                Test test = new Test();
                test.Name = i.ToString();
                test.Index = i.ToString();
                test.Time = DateTime.Now;
                _tests.Add(test);
            }
            Console.WriteLine("TaskOnethousand_One 1000條數據第一個方法 執行完成");
        }
        public static void TaskOnethousand_Two()
        {
            for (int i = 1000; i < 2000; i++)
            {
                Test test = new Test();
                test.Name = i.ToString();
                test.Index = i.ToString();
                test.Time = DateTime.Now;
                _tests.Add(test);
            }
            Console.WriteLine("TaskOnethousand_Two 1000條數據第二個方法 執行完成");
        }
        public static void TaskOnethousand_Three()
        {
            for (int i = 2000; i < 3000; i++)
            {
                Test test = new Test();
                test.Name = i.ToString();
                test.Index = i.ToString();
                test.Time = DateTime.Now;
                _tests.Add(test);
            }
            Console.WriteLine("TaskOnethousand_Three 1000條數據第三個方法 執行完成");
        }
        public static void TaskOnethousand_Four()
        {
            for (int i = 3000; i < 4000; i++)
            {
                Test test = new Test();
                test.Name = i.ToString();
                test.Index = i.ToString();
                test.Time = DateTime.Now;
                _tests.Add(test);
            }
            Console.WriteLine("TaskOnethousand_Three 1000條數據第四個方法 執行完成");
        }
        #endregion
        static void Main(string[] args)
        {
            //五百條數據順序完成
            Stopwatch swFive = new Stopwatch();
            swFive.Start();
            Thread.Sleep(3000);
            _tests = new List<Test>();
            TaskFive_One();
            TaskFive_Two();
            TaskFive_Three();
            TaskFive_Four();
            swFive.Stop();
            Console.WriteLine("500條數據  順序編程所耗時間:" + swFive.ElapsedMilliseconds);


            //五百條數據並行完成
            Stopwatch swFiveTask = new Stopwatch();
            swFiveTask.Start();
            Thread.Sleep(3000);
            _tests = new List<Test>();
            Parallel.Invoke(() => TaskFive_One(), () => TaskFive_Two(), () => TaskFive_Three(), () => TaskFive_Four());
            swFiveTask.Stop();
            Console.WriteLine("500條數據  並行編程所耗時間:" + swFiveTask.ElapsedMilliseconds);


            //一千條數據順序完成
            Stopwatch swOnethousand = new Stopwatch();
            swOnethousand.Start();
            Thread.Sleep(3000);
            _tests = new List<Test>();
            TaskOnethousand_One();
            TaskOnethousand_Two();
            TaskOnethousand_Three();
            TaskOnethousand_Four();
            swOnethousand.Stop();
            Console.WriteLine("1000條數據  順序編程所耗時間:" + swOnethousand.ElapsedMilliseconds);


            //一千條數據並行完成
            Stopwatch swOnethousandTask = new Stopwatch();
            swOnethousandTask.Start();
            Thread.Sleep(3000);
            _tests = new List<Test>();
            Parallel.Invoke(() => TaskOnethousand_One(), () => TaskOnethousand_Two(), () => TaskOnethousand_Three(), () => TaskOnethousand_Four());
            swOnethousandTask.Stop();
            Console.WriteLine("1000條數據  並行編程所耗時間:" + swOnethousandTask.ElapsedMilliseconds);
        }

 

 

我們看這次的運行結果,發現我們使用順序編程和並行編程所需要的時間相差無幾的。那麼怎麼回事呢?我們仔細檢查下,發現我們似乎對資源進行了共用。我們下麵處理下,對list集合不進行共用看看。

 

        #region Parallel.Invoke()不使用共同資源

        public static void TaskFive_One()
        {
            List<Test> tests = new List<Test>();
            for (int i = 0; i < 500; i++)
            {
                Test test = new Test();
                test.Name = "Name" + i.ToString();
                test.Index = "Index" + i.ToString();
                test.Time = DateTime.Now;
                tests.Add(test);
            }
            Console.WriteLine("TaskFive_One 500條數據第一個方法 執行完成");
        }
        public static void TaskFive_Two()
        {
            List<Test> tests = new List<Test>();
            for (int i = 500; i < 1000; i++)
            {
                Test test = new Test();
                test.Name = "Name" + i.ToString();
                test.Index = "Index" + i.ToString();
                test.Time = DateTime.Now;
                tests.Add(test);
            }
            Console.WriteLine("TaskFive_Two 500條數據第二個方法 執行完成");
        }
        public static void TaskFive_Three()
        {
            List<Test> tests = new List<Test>();
            for (int i = 1000; i < 1500; i++)
            {
                Test test = new Test();
                test.Name = "Name" + i.ToString();
                test.Index = "Index" + i.ToString();
                test.Time = DateTime.Now;
                tests.Add(test);
            }
            Console.WriteLine("TaskFive_Three 500條數據第三個方法 執行完成");
        }
        public static void TaskFive_Four()
        {
            List<Test> tests = new List<Test>();
            for (int i = 1500; i < 2000; i++)
            {
                Test test = new Test();
                test.Name = "Name" + i.ToString();
                test.Index = "Index" + i.ToString();
                test.Time = DateTime.Now;
                tests.Add(test);
            }
            Console.WriteLine("TaskFive_Four 500條數據第四個方法 執行完成");
        }



        public static void TaskOnethousand_One()
        {
            List<Test> tests = new List<Test>();
            for (int i = 0; i < 1000; i++)
            {
                Test test = new Test();
                test.Name = "Name" + i.ToString();
                test.Index = "Index" + i.ToString();
                test.Time = DateTime.Now;
                tests.Add(test);
            }
            Console.WriteLine("TaskOnethousand_One 1000條數據第一個方法 執行完成");
        }
        public static void TaskOnethousand_Two()
        {
            List<Test> tests = new List<Test>();
            for (int i = 1000; i < 2000; i++)
            {
                Test test = new Test();
                test.Name = "Name" + i.ToString();
                test.Index = "Index" + i.ToString();
                test.Time = DateTime.Now;
                tests.Add(test);
            }
            Console.WriteLine("TaskOnethousand_Two 1000條數據第二個方法 執行完成");
        }
        public static void TaskOnethousand_Three()
        {
            List<Test> tests = new List<Test>();
            for (int i = 2000; i < 3000; i++)
            {
                Test test = new Test();
                test.Name = "Name" + i.ToString();
                test.Index = "Index" + i.ToString();
                test.Time = DateTime.Now;
                tests.Add(test);
            }
            Console.WriteLine("TaskOnethousand_Three 1000條數據第三個方法 執行完成");
        }
        public static void TaskOnethousand_Four()
        {
            List<Test> tests = new List<Test>();
            for (int i = 3000; i < 4000; i++)
            {
                Test test = new Test();
                test.Name = "Name" + i.ToString();
                test.Index = "Index" + i.ToString();
                test.Time = DateTime.Now;
                tests.Add(test);
            }
            Console.WriteLine("TaskOnethousand_Four 1000條數據第四個方法 執行完成");
        }
        #endregion

 

        static void Main(string[] args)
        {
            Stopwatch swTest = new Stopwatch();
            swTest.Start();
            Thread.Sleep(3000);
            TaskFive_One();
            TaskFive_Two();
            TaskFive_Three();
            TaskFive_Four();
            swTest.Stop();
            Console.WriteLine("500條數據  順序編程所耗時間:" + swTest.ElapsedMilliseconds);


            //五百條數據並行完成 
            swTest.Restart();
            Thread.Sleep(3000);
            Parallel.Invoke(() => TaskFive_One(), () => TaskFive_Two(), () => TaskFive_Three(), () => TaskFive_Four());
            swTest.Stop();
            Console.WriteLine("500條數據  並行編程所耗時間:" + swTest.ElapsedMilliseconds);


            //一千條數據順序完成 
            swTest.Restart();
            Thread.Sleep(3000);
            TaskOnethousand_One();
            TaskOnethousand_Two();
            TaskOnethousand_Three();
            TaskOnethousand_Four();
            swTest.Stop();
            Console.WriteLine("1000條數據  順序編程所耗時間:" + swTest.ElapsedMilliseconds);


            //一千條數據並行完成 
            swTest.Restart();
            Thread.Sleep(3000);
            Parallel.Invoke(() => TaskOnethousand_One(), () => TaskOnethousand_Two(), () => TaskOnethousand_Three(), () => TaskOnethousand_Four());
            swTest.Stop();
            Console.WriteLine("1000條數據  並行編程所耗時間:" + swTest.ElapsedMilliseconds);
        }

 

 

  我們看下我們修改共用資源後,對於500條數據的運行結果,順序編程比並行編程還是要快點,但是在1000條數據的時候並行編程就明顯比順序編程要快了。而且在測試中並行編程的運行順序也是不固定的。我們在日常編程中我們需要衡量我們的應用是否需要並行編程,不然可能造成更多的性能損耗。

項目源碼地址


世界上那些最容易的事情中,拖延時間最不費力。堅韌是成功的一大要素,只要在門上敲得夠久夠大聲,終會把人喚醒的。

    歡迎大家掃描下方二維碼,和我一起學習更多的知識

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

-Advertisement-
Play Games
更多相關文章
  • 其它關於C# 8和.NET Core 3.0新特性的文章: C# 8 - Nullable Reference Types 可空引用類型 C# 8 - 模式匹配 C# 8 - Range 和 Index(範圍和索引) C# 8.0 - 預設介面方法 C# 8 - using聲明 和 非同步流 .NET ...
  • 在我們的項目中慢慢的要把資料庫的實體模型和視圖模型進行分離,防止被人拿到我們表欄位。在學校的時候自己只是有將很多資料庫模型,寫成一個視圖模型返回到前臺。 首先我們把這兩個包引入項目中去。 然後我們創建一個轉換配置類,這個類要繼承 Profile 將我們需要轉換的類寫到我們構造函數裡面去,這裡要註意我 ...
  • 這篇文章探討了讓不同的請求去使用不同的中間件,那麼我們應該如何配置ASP.NET Core中間件?其實中間件只是在ASP.NET Core中處理Web請求的管道。所有ASP.NET Core應用程式至少需要一個中間件來響應請求,並且您的應用程式實際上只是中間件的集合。當然MVC管道本身就是中間件,早 ...
  • 直接上代碼,這個過程中有個數據SqlDataReader轉為 DataTable的過程,當中為什麼這樣,是應為我直接綁定DataSource的時候沒有數據,網人家說直接綁定但是沒效果,我就轉換了一下。 //存儲過程 public static DataTable GetTableaToPROCEDU ...
  • 【易酷試用管理系統】最新版本:V1.8 程式包大小:17.5MB更新時間:2018年11月4日開發語言:ASP.NET (C#) + MSSQL運行環境:Win2008 + IIS +.NET4.5 + MSSQL2012客服QQ:166294550測試網站:http://demo.ekutms.c ...
  • Aspose.ZIP for .NET是用於標準ZIP格式的靈活文檔壓縮和存檔操作API。API使.NET應用程式能夠實現文件壓縮/解壓縮,文件存檔以及文件夾和存檔加密。它通過用戶定義的密碼和使用ZipCrypto或AES加密(例如AES128、192和AES256)的傳統加密來提供保護。 Aspo ...
  • 最近在寫一個移動端API介面,其中有一個需求:介面返回附件url地址讓手機端調用實現文件線上預覽。大體實現思路:把doc、xls等文本格式文件轉換為pdf,轉換後的pdf文件存放在伺服器上面,方便第二次調用(目前代碼只實現doc和xls文件轉換,如大家有什麼更好的方案,歡迎大家留言)。 廢話不多說, ...
  • using System; namespace program { class program1 { static void Main(string[] args) { int a = 100; int b = 100; Console.WriteLine("下麵使用豎式計算結果"); Consol ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...