ThreadPool類(線程池)

来源:https://www.cnblogs.com/scmail81/archive/2018/08/19/9503266.html
-Advertisement-
Play Games

CLR線程池並不會在CLR初始化時立即建立線程,而是在應用程式要創建線程來運行任務時,線程池才初始化一個線程。線程池初始化時是沒有線程的,線程池裡的線程的初始化與其他線程一樣,但是在完成任務以後,該線程不會自行銷毀,而是以掛起的狀態返回到線程池。直到應用程式再次向線程池發出請求時,線程池裡掛起的線程 ...


CLR線程池並不會在CLR初始化時立即建立線程,而是在應用程式要創建線程來運行任務時,線程池才初始化一個線程。
線程池初始化時是沒有線程的,線程池裡的線程的初始化與其他線程一樣,但是在完成任務以後,該線程不會自行銷毀,而是以掛起的狀態返回到線程池。直到應用程式再次向線程池發出請求時,線程池裡掛起的線程就會再度激活執行任務。
這樣既節省了建立線程所造成的性能損耗,也可以讓多個任務反覆重用同一線程,從而在應用程式生存期內節約大量開銷。

通過CLR線程池所建立的線程總是預設為後臺線程,優先順序數為ThreadPriority.Normal。

CLR線程池分為工作者線程(workerThreads)I/O線程(completionPortThreads)兩種:

  • 工作者線程是主要用作管理CLR內部對象的運作,通常用於計算密集的任務。
  • I/O(Input/Output)線程主要用於與外部系統交互信息,如輸入輸出,CPU僅需在任務開始的時候,將任務的參數傳遞給設備,然後啟動硬體設備即可。等任務完成的時候,CPU收到一個通知,一般來說是一個硬體的中斷信號,此時CPU繼續後繼的處理工作。在處理過程中,CPU是不必完全參與處理過程的,如果正在運行的線程不交出CPU的控制權,那麼線程也只能處於等待狀態,即使操作系統將當前的CPU調度給其他線程,此時線程所占用的空間還是被占用,而並沒有CPU處理這個線程,可能出現線程資源浪費的問題。如果這是一個網路服務程式,每一個網路連接都使用一個線程管理,可能出現大量線程都在等待網路通信,隨著網路連接的不斷增加,處於等待狀態的線程將會很消耗盡所有的記憶體資源。可以考慮使用線程池解決這個問題。

  線程池的最大值一般預設為1000、2000。當大於此數目的請求時,將保持排隊狀態,直到線程池裡有線程可用。

  使用CLR線程池的工作者線程一般有兩種方式:

  • 通過ThreadPool.QueueUserWorkItem()方法;
  • 通過委托;

  要註意,不論是通過ThreadPool.QueueUserWorkItem()還是委托,調用的都是線程池裡的線程。

通過以下兩個方法可以讀取和設置CLR線程池中工作者線程與I/O線程的最大線程數。

  1. ThreadPool.GetMax(out in workerThreads,out int completionPortThreads);
  2. ThreadPool.SetMax(int workerThreads,int completionPortThreads);

  若想測試線程池中有多少線程正在投入使用,可以通過ThreadPool.GetAvailableThreads(out in workThreads,out int conoletionPortThreads)方法。

方法 說明
GetAvailableThreads 剩餘空閑線程數
GetMaxThreads 最多可用線程數,所有大於此數目的請求將保持排隊狀態,直到線程池線程變為可用
GetMinThreads 檢索線程池在新請求預測中維護的空閑線程數
QueueUserWorkItem 啟動線程池裡得一個線程(隊列的方式,如線程池暫時沒空閑線程,則進入隊列排隊)
SetMaxThreads 設置線程池中的最大線程數
SetMinThreads 設置線程池最少需要保留的線程數

我們可以使用線程池來解決上面的大部分問題,跟使用單個線程相比,使用線程池有如下優點:

1、縮短應用程式的響應時間。因為線上程池中有線程的線程處於等待分配任務狀態(只要沒有超過線程池的最大上限),無需創建線程。

2、不必管理和維護生存周期短暫的線程,不用在創建時為其分配資源,在其執行完任務之後釋放資源。

3、線程池會根據當前系統特點對池內的線程進行優化處理。

總之使用線程池的作用就是減少創建和銷毀線程的系統開銷。在.NET中有一個線程的類ThreadPool,它提供了線程池的管理。

ThreadPool是一個靜態類,它沒有構造函數,對外提供的函數也全部是靜態的。其中有一個QueueUserWorkItem方法,它有兩種重載形式,如下:

public static bool QueueUserWorkItem(WaitCallback callBack):將方法排入隊列以便執行。此方法在有線程池線程變得可用時執行。

public static bool QueueUserWorkItem(WaitCallback callBack,Object state):將方法排入隊列以便執行,並指定包含該方法所用數據的對象。此方法在有線程池線程變得可用時執行。

QueueUserWorkItem方法中使用的的WaitCallback參數表示一個delegate,它的聲明如下:

public delegate void WaitCallback(Object state)

如果需要傳遞任務信息可以利用WaitCallback中的state參數,類似於ParameterizedThreadStart委托。

下麵是一個ThreadPool的例子,代碼如下:

using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;

namespace ConsoleApp1
{
    class ThreadPoolDemo
    {
        public ThreadPoolDemo()
        {
        }

        public void Work()
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(CountProcess));
            ThreadPool.QueueUserWorkItem(new WaitCallback(GetEnvironmentVariables));
        }
        /// <summary>  
        /// 統計當前正在運行的系統進程信息  
        /// </summary>  
        /// <param name="state"></param>  
        private void CountProcess(object state)
        {
            Process[] processes = Process.GetProcesses();
            foreach (Process p in processes)
            {
                try
                {
                    Console.WriteLine("進程信息:Id:{0},ProcessName:{1},StartTime:{2}", p.Id, p.ProcessName, p.StartTime);
                }
                catch (Win32Exception e)
                {
                    Console.WriteLine("ProcessName:{0}", p.ProcessName);
                }
                finally
                {
                }
            }
            Console.WriteLine("獲取進程信息完畢。");
        }
        /// <summary>  
        /// 獲取當前機器系統變數設置  
        /// </summary>  
        /// <param name="state"></param>  
        public void GetEnvironmentVariables(object state)
        {
            IDictionary list = System.Environment.GetEnvironmentVariables();
            foreach (DictionaryEntry item in list)
            {
                Console.WriteLine("系統變數信息:key={0},value={1}", item.Key, item.Value);
            }
            Console.WriteLine("獲取系統變數信息完畢。");
        }
    }
}
ThreadPoolDemo
using System;
using System.Threading;

namespace ConsoleApp1
{

    class Program
    {
        static void Main(string[] args)
        {
            ThreadPoolDemo tpd1 = new ThreadPoolDemo();
            tpd1.Work();
            Thread.Sleep(5000);
            Console.WriteLine("OK");
            Console.ReadLine();
        }
    }
}
Program

 

利用ThreadPool調用工作線程和IO線程的範例

using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Threading;

namespace ConsoleApp1
{

    class Program
    {
        static void Main(string[] args)
        {
            // 設置線程池中處於活動的線程的最大數目
            // 設置線程池中工作者線程數量為1000,I/O線程數量為1000
            ThreadPool.SetMaxThreads(1000, 1000);
            Console.WriteLine("Main Thread: queue an asynchronous method");
            PrintMessage("Main Thread Start");

            // 把工作項添加到隊列中,此時線程池會用工作者線程去執行回調方法            
            ThreadPool.QueueUserWorkItem(asyncMethod);
            asyncWriteFile();
            Console.Read();
        }

        // 方法必須匹配WaitCallback委托
        private static void asyncMethod(object state)
        {
            Thread.Sleep(1000);
            PrintMessage("Asynchoronous Method");
            Console.WriteLine("Asynchoronous thread has worked ");
        }


        #region 非同步讀取文件模塊
        private static void asyncReadFile()
        {
            byte[] byteData = new byte[1024];
            FileStream stream = new FileStream(@"D:\123.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite, 1024, true);
            //把FileStream對象,byte[]對象,長度等有關數據綁定到FileDate對象中,以附帶屬性方式送到回調函數
            Hashtable ht = new Hashtable();
            ht.Add("Length", (int)stream.Length);
            ht.Add("Stream", stream);
            ht.Add("ByteData", byteData);

            //啟動非同步讀取,倒數第二個參數是指定回調函數,倒數第一個參數是傳入回調函數中的參數
            stream.BeginRead(byteData, 0, (int)ht["Length"], new AsyncCallback(Completed), ht);
            PrintMessage("asyncReadFile Method");
        }

        //實際參數就是回調函數
        static void Completed(IAsyncResult result)
        {
            Thread.Sleep(2000);
            PrintMessage("asyncReadFile Completed Method");
            //參數result實際上就是Hashtable對象,以FileStream.EndRead完成非同步讀取
            Hashtable ht = (Hashtable)result.AsyncState;
            FileStream stream = (FileStream)ht["Stream"];
            int length = stream.EndRead(result);
            stream.Close();
            string str = Encoding.UTF8.GetString(ht["ByteData"] as byte[]);
            Console.WriteLine(str);
            stream.Close();
        }
        #endregion

        #region 非同步寫入文件模塊
        //非同步寫入模塊
        private static void asyncWriteFile()
        {
            //文件名 文件創建方式 文件許可權 文件進程共用 緩衝區大小為1024 是否啟動非同步I/O線程為true
            FileStream stream = new FileStream(@"D:\123.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite, 1024, true);
            //這裡要註意,如果寫入的字元串很小,則.Net會使用輔助線程寫,因為這樣比較快
            byte[] bytes = Encoding.UTF8.GetBytes("你在他鄉還好嗎?");
            //非同步寫入開始,倒數第二個參數指定回調函數,最後一個參數將自身傳到回調函數里,用於結束非同步線程
            stream.BeginWrite(bytes, 0, (int)bytes.Length, new AsyncCallback(Callback), stream);
            PrintMessage("AsyncWriteFile Method");
        }

        static void Callback(IAsyncResult result)
        {
            //顯示線程池現狀
            Thread.Sleep(2000);
            PrintMessage("AsyncWriteFile Callback Method");
            //通過result.AsyncState再強制轉換為FileStream就能夠獲取FileStream對象,用於結束非同步寫入
            FileStream stream = (FileStream)result.AsyncState;
            stream.EndWrite(result);
            stream.Flush();
            stream.Close();
            asyncReadFile();
        }
        #endregion

        // 列印線程池信息
        private static void PrintMessage(String data)
        {
            int workthreadnumber;
            int iothreadnumber;

            // 獲得線程池中可用的線程,把獲得的可用工作者線程數量賦給workthreadnumber變數
            // 獲得的可用I/O線程數量給iothreadnumber變數
            ThreadPool.GetAvailableThreads(out workthreadnumber, out iothreadnumber);

            Console.WriteLine("{0}\n CurrentThreadId is {1}\n CurrentThread is background :{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is: {4}\n",
                data,
                Thread.CurrentThread.ManagedThreadId,
                Thread.CurrentThread.IsBackground.ToString(),
                workthreadnumber.ToString(),
                iothreadnumber.ToString());
        }
    }
}
Program

 

線程池中放入非同步操作

using System;
using System.Threading;

namespace ConsoleApp1
{

    class Program
    {
        private static void AsyncOperation(object state)
        {
            Console.WriteLine("Operation state: {0}", state ?? "(null)");
            Console.WriteLine("Worker thread id: {0}", Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(TimeSpan.FromSeconds(2));
        }

        static void Main(string[] args)
        {
            const int x = 1;
            const int y = 2;
            const string lambdaState = "lambda state 2";

            ThreadPool.QueueUserWorkItem(AsyncOperation);
            Thread.Sleep(TimeSpan.FromSeconds(1));

            ThreadPool.QueueUserWorkItem(AsyncOperation, "async state");
            Thread.Sleep(TimeSpan.FromSeconds(1));

            ThreadPool.QueueUserWorkItem(state => {
                Console.WriteLine("Operation state: {0}", state);
                Console.WriteLine("Worker thread id: {0}", Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(TimeSpan.FromSeconds(2));
            }, "lambda state");

            ThreadPool.QueueUserWorkItem(_ =>
            {
                Console.WriteLine("Operation state: {0}, {1}", x + y, lambdaState);
                Console.WriteLine("Worker thread id: {0}", Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(TimeSpan.FromSeconds(2));
            }, "lambda state");

            Thread.Sleep(TimeSpan.FromSeconds(2));
        }
    }
}
Program

 

線程池同步操作

using System;
using System.Threading;

namespace ConsoleApp1
{
    class ThreadPoolDemo
    {
        static object lockobj = new object();
        static int Count = 0;
        ManualResetEvent manualEvent;
        public ThreadPoolDemo(ManualResetEvent manualEvent)
        {
            this.manualEvent = manualEvent;
        }
        public void DisplayNumber(object a)
        {

            lock (lockobj)
            {
                Count++;
                Console.WriteLine("當前運算結果:{0},Count={1},當前子線程id:{2} 的狀態:{3}", a, Count, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.ThreadState);
            }
            //Console.WriteLine("當前運算結果:{0}", a);
            //Console.WriteLine("當前運算結果:{0},當前子線程id:{1} 的狀態:{2}", a,Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.ThreadState);
            //這裡是方法執行時間的模擬,如果註釋該行代碼,就能看出線程池的功能了
            Thread.Sleep(2000);
            //Console.WriteLine("當前運算結果:{0},Count={1},當前子線程id:{2} 的狀態:{3}", a, Count, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.ThreadState);
            //這裡是釋放共用鎖,讓其他線程進入
            manualEvent.Set();


        }
    }
}
ThreadPoolDemo
using System;
using System.Diagnostics;
using System.Threading;

namespace ConsoleApp1
{

    class Program
    {
        //設定任務數量 
        static int count = 10;
        static void Main(string[] args)
        {
            //讓線程池執行5個任務所以也為每個任務加上這個對象保持同步
            ManualResetEvent[] events = new ManualResetEvent[count];
            Console.WriteLine("當前主線程id:{0}", Thread.CurrentThread.ManagedThreadId);

            Stopwatch sw = new Stopwatch();
            sw.Start();
            NoThreadPool(count);
            sw.Stop();
            Console.WriteLine("Execution time using threads: {0}", sw.ElapsedMilliseconds);


            sw.Reset();
            sw.Start();
            //迴圈每個任務
            for (int i = 0; i < count; i++)
            {
                //實例化同步工具
                events[i] = new ManualResetEvent(false);
                //Test在這裡就是任務類,將同步工具的引用傳入能保證共用區內每次只有一個線程進入
                ThreadPoolDemo tst = new ThreadPoolDemo(events[i]);
                //Thread.Sleep(200);
                //將任務放入線程池中,讓線程池中的線程執行該任務                 
                ThreadPool.QueueUserWorkItem(tst.DisplayNumber, i);
            }
            //註意這裡,設定WaitAll是為了阻塞調用線程(主線程),讓其餘線程先執行完畢,
            //其中每個任務完成後調用其set()方法(收到信號),當所有
            //的任務都收到信號後,執行完畢,將控制權再次交回調用線程(這裡的主線程)
            ManualResetEvent.WaitAll(events);
            sw.Stop();
            Console.WriteLine("Execution time using threads: {0}", sw.ElapsedMilliseconds);
            //Console.WriteLine("所有任務做完!");
            Console.ReadKey();
        }

        static void NoThreadPool(int count)
        {
            for (int i = 0; i < count; i++)
            {
                Thread.Sleep(2000);
                Console.WriteLine("當前運算結果:{0},Count={1},當前子線程id:{2} 的狀態:{3}", i, i + 1, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.ThreadState);
            }
        }

    }
}
Program

 

線程池中的取消操作

using System;
using System.Threading;

namespace ConsoleApp1
{

    class Program
    {
        static void Main(string[] args)
        {
            ThreadPool.SetMaxThreads(1000, 1000);
            Console.WriteLine("Main thread run");
            PrintMessage("Start");
            Run();
            Console.ReadKey();
        }

        private static void Run()
        {
            CancellationTokenSource cts = new CancellationTokenSource();

            // 這裡用Lambda表達式的方式和使用委托的效果一樣的,只是用了Lambda後可以少定義一個方法。
            // 這在這裡就是讓大家明白怎麼lambda表達式如何由委托轉變的
            ////ThreadPool.QueueUserWorkItem(o => Count(cts.Token, 1000));
            ThreadPool.QueueUserWorkItem(callback, cts.Token);

            Console.WriteLine("Press Enter key to cancel the operation\n");
            Console.ReadLine();

            // 傳達取消請求            
            cts.Cancel();
            Console.ReadLine();
        }

        private static void callback(object state)
        {
            Thread.Sleep(1000);
            PrintMessage("Asynchoronous Method Start");
            CancellationToken token = (CancellationToken)state;
            Count(token, 1000);
        }

        // 執行的操作,當受到取消請求時停止數數
        private static void Count(CancellationToken token, int countto)
        {
            for (int i = 0; i < countto; i++)
            {
                if (token.IsCancellationRequested)
                {
                    Console.WriteLine("Count is canceled");
                    break;
                }

                Console.WriteLine(i);
                Thread.Sleep(300);
            }

            Console.WriteLine("Cout has done");
        }

        // 列印線程池信息
        private static void PrintMessage(String data)
        {
            int workthreadnumber;
            int iothreadnumber;

            // 獲得線程池中可用的線程,把獲得的可用工作者線程數量賦給workthreadnumber變數
            // 獲得的可用I/O線程數量給iothreadnumber變數
            ThreadPool.GetAvailableThreads(out workthreadnumber, out iothreadnumber);

            Console.WriteLine("{0}\n CurrentThreadId is {1}\n CurrentThread is background :{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is: {4}\n",
                data,
                Thread.CurrentThread.ManagedThreadId,
                Thread.CurrentThread.IsBackground.ToString(),
                workthreadnumber.ToString(),
                iothreadnumber.ToString());
        }
    }
}
Program

 

Thread與ThreadPool的一個性能比較

using System;
using System.Diagnostics;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            const int numberOfOperations = 300;
            var sw = new Stopwatch();
            sw.Start();
            UseThreads(numberOfOperations);
            sw.Stop();
            Console.WriteLine("Execution time using threads: {0}", sw.ElapsedMilliseconds);

            sw.Reset();
            sw.Start();
            UseThreadPool(numberOfOperations);
            sw.Stop();
            Console.WriteLine("Execution time using threadPool: {0}", sw.ElapsedMilliseconds);
        }

        static void UseThreads(int numberOfOperations)
        {
            using (var countdown = new CountdownEvent(numberOfOperations))
            {
                Console.WriteLine("Scheduling work by creating threads");
                for (int i = 0; i < numberOfOperations; i++)
                {
                    var thread = new Thread(() => {
                        Console.Write("{0},", Thread.CurrentThread.ManagedThreadId);
                        Thread.Sleep(TimeSpan.FromSeconds(0.1));
                        countdown.Signal();
                    });
                    thread.Start();
                }
                countdown.Wait();
                Console.WriteLine();
            }
        }

        static void UseThreadPool(int numberOfOperations)
        {
            using (var countdown = new CountdownEvent(numberOfOperations))
            {
                Console.WriteLine("Starting work on a threadpool");
                for (int i = 0; i < numberOfOperations; i++)
                {
                    ThreadPool.QueueUserWorkItem(_ => {
                        Console.Write("{0},", Thread.CurrentThread.ManagedThreadId);
                        Thread.Sleep(TimeSpan.FromSeconds(0.1));
                        countdown.Signal();
                    });
                }
                countdown.Wait();
                Console.WriteLine();
            }
        }
    }
}
Program

 

 

 

 

 


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

-Advertisement-
Play Games
更多相關文章
  • 一、包裝類 java是一門面向對象的語言,秉承一切皆對象的思想。 可java中有一些基本數據類型並不是對象,有時可能需要將它們變為對象。 這時就需要用到我們的包裝類了。 通過對應的包裝類可以讓基本屬性擁有對象的特性,之後可以使用相關的操作。 自動裝箱與自動拆箱 通過上面代碼我們可以看出,都是手動裝箱 ...
  • Guido van Rossum,Python之父,就是他用一部英國喜劇《蒙提·派森的飛行馬戲團》(Monty Python and the Flying Circus)命名了這門語言。1989年,他在荷蘭創造了Python(果然名字里有Van的都是荷蘭人嗎); 1991年初,Python發佈了第一 ...
  • 接上一篇,前兩篇解決中文的問題主要是在字元集上做的手腳,即將中文轉成英文,但是有一種情況我們都來不及做轉換,即登錄時伺服器直接返回了中文內容: 此時程式報瞭如下錯誤,其實還是字元集問題: 為此:我們可以在接收數據的時候直接對其進行異常捕捉,如果異常則換一種解碼方式: 上一篇:ssh.invoke_s ...
  • 信號signal 是python進程間進行信號發送的一種機制,其原理是操作系統對進程的控制,是一種程式中斷 一個進程一旦接收到信號就會打斷原來的程式執行流程來處理信號。 那麼singanl到底有什麼用呢? siganl的應用: 1. 故障定位技術(進程的底層故障,例如進程突然中斷和一些可能性較小的故 ...
  • luogu原題 最近剛學了博弈論,拿來練練手qwq 其實和數值的大小並沒有關係 我們用$N/P$態來表示必勝/必敗狀態 先在草稿紙上探究硬幣在最左側(其實左右側是等價的)的一條長鏈的$N/P$態,設鏈長為$n$ 我們用$1$代替其他所有非$0$數 $n=2: 11$ $N$態 $n=3: 111$ ...
  • 近一個月一直在寫業務,空閑時間刷刷leetcode,刷題過程中遇到了一道比較有意思的題目,和大家分享。 題目描述: 給定兩個整數,被除數 dividend 和除數 divisor。將兩數相除,要求不使用乘法、除法和 mod 運算符。返回被除數 dividend 除以除數 divisor 得到的商。 ...
  • 廢話不說,直接開門見山! 需要在WebContent下的lib下導入兩個包 mybatis-3.2.5.jar ojdbc6.jar 1 package com.xdl.entity; 2 3 import java.io.Serializable; 4 5 public class Dept im ...
  • 使用mapper代理方式開發: 需要編寫mapper介面,UserMapper.java需要編寫映射文件,UserMapper.xml需要遵循一些開發規範,mybatis便可以自動生成mapper介面實現類代理對象 遵循的開發規範:1:UserMapper.xml中namespace命名空間 與 U ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...