線程(thread)

来源:https://www.cnblogs.com/jonins/archive/2018/07/24/9336181.html
-Advertisement-
Play Games

線程概述 線程是一個獨立處理的執行路徑。每個線程都運行在一個操作系統進程中,這個進程是程式執行的獨立環境。在單線程中進程的獨立環境內只有一個線程運行,所以該線程具有獨立使用進程資源的權利。在多線程程式中,在進程中有多個線程運行,所以它們共用同一個執行環境。 基礎線程(thread) 使用Thread ...


線程概述

線程是一個獨立處理的執行路徑。每個線程都運行在一個操作系統進程中,這個進程是程式執行的獨立環境。在單線程中進程的獨立環境內只有一個線程運行,所以該線程具有獨立使用進程資源的權利。在多線程程式中,在進程中有多個線程運行,所以它們共用同一個執行環境。

 

基礎線程(thread)

使用Thread類可以創建和控制線程,定義在System.Threading命名空間中:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             int mainId = Thread.CurrentThread.ManagedThreadId;
 6             Console.WriteLine("主線程Id為:{0}", mainId);
 7             //定義線程
 8             Thread thread = new Thread(() =>
 9             {
10                 Test("Demo-ok");
11             });
12             //啟動線程
13             thread.Start();
14             Console.WriteLine("主線程Id為:{0}", mainId);
15             Console.ReadKey();
16         }
17         static void Test(string o)
18         {
19             Console.WriteLine("工作者線程Id為:{0}", Thread.CurrentThread.ManagedThreadId);
20             Console.WriteLine("執行方法:{0}", o);
21         }
22         /*
23          * 作者:Jonins
24          * 出處:http://www.cnblogs.com/jonins/
25          */
26     }

執行結果(執行結果並不固定):

主線程創建一個新線程thread在上面運行一個方法Test。同時主線程也會繼續執行。在單核電腦上,操作系統會給每一個線程分配一些"時間片"(winodws一般為20毫秒),用於模擬併發性。而在多核/多處理器主機上線程卻能夠真正實現並行執行(分別由電腦上其它激活處理器完成)。

 

線程常用方法

Thread在.NET Framework 1.1起引入是最早的多線程處理方式,他包含了幾種最常用的方法如下,

Start 開啟線程(停止後的線程無法再次啟用)
Suspend 暫停(掛起)線程(已過時,不推薦使用)
Resume 恢復暫停(掛起)的線程(已過時,不推薦使用)
Intterupt 中斷線程
Abort 銷毀線程
IsAlive 獲取當前線程的執行狀態(True-運行,False-停止)
Join

方法是非靜態方法,使得在系統調用此方法時只有這個線程執行完後,才能執行其他線程,包括主線程的終止!

或者給它制定時間,即最多過了這麼多時間後,如果還是沒有執行完,下麵的線程可以繼續執行而不必再理會當前線程是否執行完。

Thread.Sleep

方式是Thread類靜態方法,在調用出使得該線程暫停一段時間

 註意

不要使用Suspend和Resume方法來同步線程的活動。當你Suspend線程時,您無法知道線程正在執行什麼代碼。如果在安全許可權評估期間線程持有鎖時掛起線程,則AppDomain中的其他線程可能會被阻塞。如果線程在執行類構造函數時Suspend,則試圖使用該類的AppDomain中的其他線程將被阻塞。死鎖很容易發生。

 

後臺/前臺線程 &阻塞

前臺進程和後臺進程使用IsBackground屬性設置。此狀態與線程的優先順序(執行時間分配)無關。
前臺進程:Thread預設為前臺線程,程式關閉後,線程仍然繼續,直到計算完為止。
後臺進程:將IsBackground屬性設置為true,即為後臺進程,主線程關閉,所有子線程無論運行完否,都馬上關閉。

線程阻塞是指線程由於特定原因暫停執行,如Sleeping或執行Join後等待另一個線程停止。阻塞的線程會立刻交出”時間片“, 並從此時開始不再消耗處理器的時間,直至阻塞條件結束。使用線程的ThreadState屬性,可以測試線程的阻塞狀態。

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Thread thread = new Thread(() =>
 6             {
 7                 Test("Demo-ok");
 8             });
 9             var state = thread.ThreadState;
10             Console.WriteLine("子線程開啟前ThreadState:{0}", state);
11             //開啟線程
12             thread.Start();
13             state = thread.ThreadState;
14             Console.WriteLine("子線程開啟後ThreadState:{0}", state);
15             //阻塞主線程1秒
16             Thread.Sleep(1000);
17             state = thread.ThreadState;
18             Console.WriteLine("子線程阻塞時ThreadState:{0}", state);
19             //主線程等待子線程執行完成
20             thread.Join();
21             state = thread.ThreadState;
22             Console.WriteLine("子線程執行完成ThreadState:{0}", state);
23             Console.ReadKey();
24         }
25         static void Test(string o)
26         {
27             //阻塞子線程2秒
28             Thread.Sleep(2000);
29             Console.WriteLine("方法執行完成!返回值:{0}", o);
30         }
31         /*
32          * 作者:Jonins
33          * 出處:http://www.cnblogs.com/jonins/
34          */
35     }

結果如下:

ThreadState是一個標記枚舉量,我們只大約常用的記住這四個狀態即可,其它因為API中棄用了一部分如掛起等不必考慮:

Running 啟動線程
Stopped 該線程已停止
Unstarted 未開啟
WaitSleepJoin 線程受阻

 註意

1.當線程阻塞時,操作系統執行環境(線程上下文)切換,會增加負載,幅度一般在1-2毫秒左右。

2.ThreadState屬性只是用於調試程式,絕對不要用ThreadState來同步線程活動,因為線程狀態可能在測試ThreadState和獲取這個信息的時間段內發生變化。

 

線程優先順序

當多個線程同時運行時,可以對同時運行的多個線程設置優先順序,優先處理級別高的線程(一般情況下,如果有優先順序較高的線程在工作,就不會給優先順序較低的線程分配任何時間片)。
1     xxx.Priority = ThreadPriority.Normal;
線程優先順序通過Priority屬性設置,Priority屬性是一個ThreadPriority枚舉
AboveNormal 高於正常
BelowNormal 低於正常
Highest 最高
Lowest 最低
Normal 正常
普通線程的優先順序預設為Normal,主線程和其它工作線程(預設優先順序)優先順序相同,交替進行。

 

ThreadStart&ParameterizedThreadStart

Thread重載的其它四種構造函數需要帶入特殊對象,分別是ThreadStartParameterizedThreadStart類。

ThreadStart類本質是一個無參數無返回值的委托。

1 public delegate void ThreadStart();

ParameterizedThreadStart類本質是有一個object類型參數無返回值的委托。

1 public delegate void ParameterizedThreadStart(object obj);

使用方式如下:

 1    class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             int mainId = Thread.CurrentThread.ManagedThreadId;
 6             Console.WriteLine("主線程Id為:{0}", mainId);
 7             //ThreadStart構造函數創建線程
 8             {
 9                 ThreadStart threadStart = new ThreadStart(TestOne);
10                 Thread threadOne = new Thread(threadStart);
11                 threadOne.Start();
12             }
13             //ParameterizedThreadStart構造函數創建線程
14             {
15                 ParameterizedThreadStart parameterizedThreadStart = new ParameterizedThreadStart(TestTwo);
16                 Thread threadTwo = new Thread(parameterizedThreadStart);
17                 threadTwo.Start("DemoTwo-ok");
18             }
19             Console.WriteLine("主線程Id為:{0}", mainId);
20             Console.ReadKey();
21         }
22         private static void TestOne()
23         {
24             Console.WriteLine("執行方法:DemoOne-ok,工作者線程Id為:{0}", Thread.CurrentThread.ManagedThreadId);
25         }
26         private static void TestTwo(object o)
27         {
28             Console.WriteLine("執行方法:{0},工作者線程Id為:{1}", o, Thread.CurrentThread.ManagedThreadId);
29         }
30         /*
31          * 作者:Jonins
32          * 出處:http://www.cnblogs.com/jonins/
33          */
34     }

執行結果(執行結果不固定):

因為ThreadStartParameterizedThreadStart委托,所以我們也可以把符合要求的自定義委托或者內置委托進行轉換帶入構造函數。例如:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Action action = Test;
 6             Thread thread = new Thread(new ThreadStart(action));
 7             thread.Start();
 8             Console.ReadKey();
 9         }
10         private static void Test()
11         {
12             Console.WriteLine("執行方法:Demo-ok");
13         }
14     }

註意

在需要傳遞參數時ParameterizedThreadStart構造線程和使用lambda表達式構建線程有著極大的區別

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             //DemoOne();//數據被主線程修改
 6             //DemoTwo();
 7             Console.ReadKey();
 8         }
 9         static void DemoOne()
10         {
11             string message = "XXXXXX";
12             Thread thread = new Thread(() => Test(message));
13             thread.Start();
14             message = "YYYYYY";
15         }
16         static void DemoTwo()
17         {
18             
19             string message = "XXXXXX";
20             ParameterizedThreadStart parameterizedThreadStart = Test;
21             Thread thread = new Thread(parameterizedThreadStart);
22             thread.Start(message);
23             message = "YYYYYY";
24         }
25         private static void Test(object o)
26         {
27             for (int i = 0; i < 1000; i++)
28             {
29                 Console.WriteLine( o);
30             }
31         }
32         /*
33          * 作者:Jonins
34          * 出處:http://www.cnblogs.com/jonins/
35          */
36     }

上述案例對比DemoOneDemoTwo的執行結果我們可以得到:

1.使用lamdba表達式構建線程時,變數由引用捕獲,父線程中的任何更改都將影響子線程內的值。且lamda是在實際執行時捕獲變數而不是線上程開始時捕獲變數,如果在父線程中修改參數值子線程內的值也會受到影響

2.而ParameterizedThreadStart則是線上程啟動是捕獲變數,啟動後父線程修改變數值子線程內的值不會受到影響

 

本地/共用狀態

CLR會給每一個線程分配獨立的記憶體堆,從而保證本地變數的隔離。而多個線程訪問相同的對象,並對共用狀態的訪問沒有同步,此時就會出現數據爭用的問題從而引發程式間歇性錯誤,這也是多線程經常被詬病的緣由。

局部(本地)變數每個線程的記憶體堆都會創建變數副本。

如果線程擁有同一個對象實例的通用引用,那麼這些線程就會共用數據。

 1     public class ThreadInstance
 2     {
 3         //共用變數
 4         bool flag;
 5         public void Demo()
 6         {
 7             new Thread(Test).Start();//子線程執行一次方法
 8             Thread.Sleep(1000);
 9             Test();//主線程執行一次方法
10             Console.ReadKey();
11         }
12         void Test()
13         {
14             //線程內局部變數
15             bool localFlag=true;
16             Console.WriteLine("localFlag:{0}", localFlag);
17             localFlag = !localFlag;
18             if (!flag)
19             {
20                 Console.WriteLine("flag:{0}", flag);
21                 flag = !flag;
22             }
23         }
24     }

執行Demo方法結果:

因為兩個線程都在同一個ThreadInstance實例上調用方法,所以它們共用flag,因此flag變數只會列印一次。而localFlag為局部變數所以兩個線程內變數相互不影響。

註意

1.編譯器會將lambda表達式或匿名代理捕獲的局部變數轉換為域,它們會共用數據。
2.靜態域線程之間也會共用數據。

 

線程同步

在多個線程同時對同一個記憶體地址進行寫入,由於CPU時間調度上的問題,寫入數據會被多次的覆蓋,所以就要使線程同步。

線程同步:一個線程在對記憶體進行操作時,其他線程都不可以對這個記憶體地址進行操作,直到該線程完成操作, 其他線程才能對該記憶體地址進行操作。

同步結構可以分三大類:

排他鎖:排他鎖結構只允許一個線程執行特定的活動,它們的主要目標是允許線程訪問共用的寫狀態,但不會互相影響。包括(lock、Mutex、SpinLock)。

非排他鎖:非排他鎖只能實現有限的併發性。包括(Semaphore、ReaderWriterLock)。

發送信號:允許線程保持阻塞,直到從其它線程接受到通知。包括(ManualResetEvent、AutoResetEvent、CountdownEvent和Barrier)

 

排他鎖 lock&Mutex&SpinLock

1.內核鎖 Lock&Monitor

Lock:保證當多個線程同時爭奪同一個鎖時,每次只有一個線程可以鎖定同步對象,其他線程會等待(或阻塞)在加鎖位置,直到鎖釋放,其它線程才可以繼續訪問。如果多個線程爭奪同一個鎖,那麼它們會在一個準備隊列中排隊,以先到先得的方式分配鎖。排他鎖有時候也稱為對鎖保護的對象添加序列化訪問許可權,因為一個線程的訪問不會與其他線程的訪問重疊。

lock使用的示例如下,Demo未加鎖DemoTwo加鎖

 1     public class ThreadInstance
 2     {
 3         //--------------Demo----------------
 4         public void Demo()
 5         {
 6             new Thread(Test).Start();
 7             Test();
 8         }
 9         private bool Flag { get; set; }
10         void Test()
11         {
12                 Console.WriteLine("Demo-Flag:{0}", Flag);
13                 Thread.Sleep(1000);//阻塞子線程,讓主線程運行下來
14                 Flag = true;
15         }
16         //--------------DemoTwo----------------
17         public void DemoTwo()
18         {
19             new Thread(TestTow).Start();
20             TestTow();
21         }
22         private bool FlagTow { get; set; }
23         readonly object Locker = new object();
24         void TestTow()
25         {
26             //加鎖,阻塞主線程直至子線程執行完畢
27             lock (Locker)
28             {
29                     Console.WriteLine("TestTow-FlagTow:{0}", FlagTow);
30                     Thread.Sleep(1000);//阻塞子線程,讓主線程運行下來
31                     FlagTow = true;
32             }
33         }
34         /*
35          * 作者:Jonins
36          * 出處:http://www.cnblogs.com/jonins/
37          */
38     }

執行結果如下:

Demo:不具有線程安全性,兩個線程同時調用Test,會出現兩次False,因為主線程執行時子線程變數還沒有改變。

DemoTwo:保證每次只有一個線程可以鎖定同步對象(Locker),其他競爭線程(本例即主線程)都會阻塞在這個位置,直至鎖釋放,所以會列印一次False和一次True。

lock語句是Monitor.EnterMonitor.Exit方法調用try/finally語句塊的簡寫語法。

 1             lock (Locker)
 2             {
 3               ...
 4             }
 5             //-------兩者等價-------
 6             Monitor.Enter(Locker);
 7             try
 8             {
 9                ...
10             }
11             finally
12             {
13                 Monitor.Exit(Locker);
14             }

但此寫法在方法調用和語句塊之間若拋出異常,鎖將無法釋放,因為執行過程無法再進入try/finally語句塊,導致鎖泄露,優化方法是使用Monitor.Enter重載,同時可以使用Monitor.TryEnter方法指定一個超時時間。

 1         bool lockTaken = false;
 2             Monitor.Enter(Locker, ref lockTaken);
 3             try
 4             {
 5                 ...
 6             }
 7             finally
 8             {
 9                 if (lockTaken)
10                     Monitor.Exit(Locker);
11             }

2.互斥鎖 Mutex 

Mutex:類似於C#的Lock,但是它可以支持多個進程。所以Mutex可用於電腦範圍或應用範圍。使用Mutex類,就可以調用WaitOne方法獲得鎖,ReleaseMutex釋放鎖,關閉或去掉一個Mutex會自動釋放互斥鎖。

示例來自https://msdn.microsoft.com/zh-cn/library/system.threading.mutex(v=vs.110).aspx ,如需更詳細請訪問MSDN。

 1     class Program
 2     {
 3         //創建一個新的互斥。創建線程不擁有互斥對象。
 4         private static Mutex mut = new Mutex();
 5         private const int numThreads = 3;
 6         static void Main(string[] args)
 7         {
 8             //創建將使用受保護資源的線程
 9             for (int i = 0; i < numThreads; i++)
10             {
11                 Thread newThread = new Thread(new ThreadStart(ThreadProc));
12                 newThread.Name = String.Format("Thread{0} :", i + 1);
13                 newThread.Start();
14             }
15             Console.ReadKey();
16         }
17         private static void ThreadProc()
18         {
19             Console.WriteLine("{0}請求互斥鎖", Thread.CurrentThread.Name);
20             // 等待,直到安全進入,如果請求超時,不會獲得互斥量
21             if (mut.WaitOne(3000))            
22             {
23                 Console.WriteLine("{0}進入保護區了", Thread.CurrentThread.Name);
24                 {
25                     //模擬一些工作
26                     Thread.Sleep(2000);
27                     Console.WriteLine("{0}執行了工作 ", Thread.CurrentThread.Name);
28                 }
29                 // 釋放互斥鎖。
30                 mut.ReleaseMutex();
31                 Console.WriteLine("{0}釋放了互斥鎖 ", Thread.CurrentThread.Name);
32             }
33             else
34             {
35                 Console.WriteLine("{0}不會獲得互斥量", Thread.CurrentThread.Name);
36             }
37         }
38     }

註意:

1.給Mutex命名,使之整個電腦範圍有效,這個名稱應該在公司和應用程式中保持唯一。

2.獲得和釋放一個無爭奪的Mutex需要幾毫秒,時間比lock操作慢50倍。

3.自旋鎖 SpinLock

 SpinLock 在.NET 4.0引入,內部實現了微優化,可以減少高度併發場景的上下文切換。示例如下:

 1     class ThreadInstance
 2     {
 3         public void Demo()
 4         {
 5             Thread thread = new Thread(() => Test());
 6             thread.Start();
 7             Test();
 8             Console.ReadKey();
 9         }
10         SpinLock spinLock = new SpinLock();
11         bool Flag;
12         void Test()
13         {
14             bool gotLock = false;     //釋放成功
15             //進入鎖
16             spinLock.Enter(ref gotLock);
17             {
18                 Console.WriteLine(Flag);
19                 Flag = !Flag;
20             }
21             if (gotLock) spinLock.Exit();//釋放鎖
22         }
23     }

執行結果如下,若註釋掉代碼行spinLock.Enter(ref gotLock);這段程式就會出現問題會列印兩次False:

排他鎖總結

  lock(內核鎖)
本質 基於內核對象構造的鎖機制,它發現資源被鎖住時,請求進入排隊等待,直到鎖釋放再繼續訪問資源
優點 CPU利用最大化。
缺點 線程上下文切換損耗性能。
  Mutex(互斥鎖)
本質 多線程共用資源時,當一個線程占用Mutex對象時,其它需要占用Mutex的線程將處於掛起狀態,直到Mutex被釋放。
優點

可以跨應用程式邊界對資源進行獨占訪問,即可以用同步不同進程中的線程。

缺點 犧牲更多的系統資源。
  SpinLock(自旋鎖)
本質 不會讓線程休眠,而是一直迴圈嘗試對資源的訪問,直到鎖釋放資源得到訪問。
優點 被阻塞時,不進行上下文切換,而是空轉等待。對多核CPU而言,減少了切換線程上下文的開銷。
缺點 長時間的迴圈導致CPU的浪費,高併發競爭下,CPU的損耗嚴重。

 

非排他鎖 SemaphoreSlim&ReaderWriterLockSlim

1.信號量 SemaphoreSlim

信號量(SemaphoreSlim)類似於一個閥門,只允許特定容量的線程進入,超出容量的線程則不允許再進入只能在後面排隊(先到先進)。容量為1的信號量與Mutexlock相似,但是信號量與線程無關,任何線程都可以釋放,而Mutexlock,只有獲得鎖的線程才可以釋放。

下麵示例5個線程同時請求但只有3個線程可以同時訪問:

 1     class Program
 2     {
 3         /// <summary>
 4         /// 聲明信號量,容量3
 5         /// </summary>
 6         static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(3);
 7         static void Main(string[] args)
 8         {
 9             for (int i = 0; i < 5; i++)
10             {
11                 new Thread(Enter).Start(i);
12             }
13             Console.ReadKey();
14         }
15         static void Enter(object id)
16         {
17             Console.WriteLine("準備訪問:{0}", id);
18             semaphoreSlim.Wait();
19             //只有3個線程可以同時訪問
20             {
21                 Console.WriteLine("開始訪問:{0}", id);
22                 Thread.Sleep(1000 * (int)id);
23                 Console.WriteLine("已經離開:{0}", id);
24             }
25             semaphoreSlim.Release();
26         }
27     }

信號量可限制併發處理,防止太多線程同時執行特定代碼。這個類有兩個功能相似的版本:SemaphoreSemaphoreSlim。後者是.NET 4.0引入的,進行了一些優化,以滿足並行編程的低延遲要求。SemaphoreSlim適用於傳統多線程編程,因為它可以再等待時指定一個取消令牌。然而它並不適用於進程間通信。Semaphore在調用WaitOne或Release時需要消耗約1毫秒時間,而SemaphoreSlim的延遲時間只有前者1/4。

2.讀/寫鎖 ReaderWriterLockSlim

一些資源訪問,當讀操作很多而寫操作很少時,限制併發訪問並不合理,這種情況可能發生在業務應用伺服器,它會將常用的數據緩存在靜態域中,用以加塊訪問速度。使用ReaderWriterLockSlim類,可以在這種情況中實現鎖的最大可用性。

ReaderWriterLockSlim在.NET 3.5引入,目的是替換ReaderWriterLock類。兩者功能相似,但後者執行速度要慢好幾倍,且本身存在一些鎖升級處理的設計缺陷。與常規鎖(lock)相比,ReaderWriterLockSlim執行速度仍然要慢一倍。

下麵示例,有3個線程不停的獲取鏈表內元素總個數,同時有2個線程每個1秒鐘向鏈表添加隨機數:

 1     class Program
	   

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

-Advertisement-
Play Games
更多相關文章
  • 問題: 這幾天在裝.NET 的開發環境,在裝好VS2013和Oracle 11g之後,做了一個測試項目,運行調試沒問題 但是涉及到資料庫相關操作,如新建數據集、連接資料庫等在調試的時候則會出現如下錯誤: 目前百度之後現存的解決方案: 1. oracle odp.net 32位/64位版本的問題 "解 ...
  • https://www.cnblogs.com/cmt/p/4692920.html ...
  • 因個人需求,需要將html格式轉換成PDF並加上水印圖片。於是乎第一次接觸這種需求的小菜鳥博主我,在某度搜索引擎上不斷的查閱關鍵字資料、踩坑,終於有了一個相應的解決方案。以下是解決步驟,記錄下來方便以後的回顧,以及各位大神們的品鑒。 1、在 NuGet 搜索 itextsharp 關鍵字 下載以下截 ...
  • 對.Net Core的學習和實踐,已經進行了一年多的世間,截止目前,微軟已經發佈.Net Core2.1,關於.NetCore的應用部署的文章比比皆是。今天藉此,回顧下.net core環境的部署過程。 首先,我這邊採用的是CentOS7+上的版本,.net core2.1。在動手前,我們先做這樣的 ...
  • 在很早之前就介紹過圖表插件Highcharts的使用了,在2014年的隨筆《基於MVC4+EasyUI的Web開發框架經驗總結(4)--使用圖表控制項Highcharts》,這裡基本上都介紹的比較完整,基本的設置也沒有太大的差異,本篇介紹的是基於Bootstrap開發框架的界面處理,以及對圖表插件Hi... ...
  • 0.簡介 Abp 框架在其內部實現了倉儲模式,並且支持 EF Core 與 Dapper 來進行資料庫連接與管理,你可以很方便地通過註入通用倉儲來操作你的數據,而不需要你自己來為每一個實體定義單獨的倉儲的實現,通用倉儲包含了常用的 CRUD 介面和一些常用方法。 例如: 1.通用倉儲定義與實現 在 ...
  • 在IIS上部署web api 完成後,瀏覽時出現了“The compiler failed with error code -2146232576.”的錯誤(有時會出現這個情況)。主要是 我們在.Net Framework 下創建Web API的時候,選擇預設的模板後,VS在創建項目時會添加一些組件 ...
  • 實際開發中有很多項目需要引用第三方的dll或者資源文件,且文件比較多,在運行時這些文件需要被拷貝到BIN目錄。 使用VS自帶的"複製到輸出目錄",似然方便,但是比較不零活,經過多次摸索,終於有了一個很好的解決辦法。 將csproj中copy指令的用法如下: 1、記事本打開啟動項目的 csproj文件 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...