C# 多線程及同步簡介示例

来源:http://www.cnblogs.com/yswenli/archive/2017/08/24/7421475.html
-Advertisement-
Play Games

60年代,在OS中能擁有資源和獨立運行的基本單位是進程,然而隨著電腦技術的發展,進程出現了很多弊端,一是由於進程是資源擁有者,創建、撤消與切換存在較大的時空開銷,因此需要引入輕型進程;二是由於對稱多處理機(SMP)出現,可以滿足多個運行單位,而多個進程並行開銷過大。 因此在80年代,出現了能獨立運 ...


       60年代,在OS中能擁有資源和獨立運行的基本單位是進程,然而隨著電腦技術的發展,進程出現了很多弊端,一是由於進程是資源擁有者,創建、撤消與切換存在較大的時空開銷,因此需要引入輕型進程;二是由於對稱多處理機(SMP)出現,可以滿足多個運行單位,而多個進程並行開銷過大。 因此在80年代,出現了能獨立運行的基本單位——線程(Threads)。        線程,有時被稱為輕量級進程(Lightweight Process,LWP),是程式執行流的最小單元。一個標準的線程由線程ID,當前指令指針(PC),寄存器集合和堆棧組成。另外,線程是進程中的一個實體,是被系統獨立調度和分派的基本單位,線程自己不擁有系統資源,只擁有一點兒在運行中必不可少的資源,但它可與同屬一個進程的其它線程共用進程所擁有的全部資源。一個線程可以創建和撤消另一個線程,同一進程中的多個線程之間可以併發執行。由於線程之間的相互制約,致使線程在運行中呈現出間斷性。線程也有就緒阻塞運行三種基本狀態。就緒狀態是指線程具備運行的所有條件,邏輯上可以運行,在等待處理機;運行狀態是指線程占有處理機正在運行;阻塞狀態是指線程在等待一個事件(如某個信號量),邏輯上不可執行。每一個程式都至少有一個線程,若程式只有一個線程,那就是程式本身。        線程是程式中一個單一的順序控制流程。進程內一個相對獨立的、可調度的執行單元,是系統獨立調度和分派CPU的基本單位指運行中的程式的調度單位。在單個程式中同時運行多個線程完成不同的工作,稱為多線程

 

一、線程簡義

1、進程與線程:進程作為操作系統執行程式的基本單位,擁有應用程式的資源,進程包含線程,進程的資源被線程共用,線程不擁有資源。

2、前臺線程和後臺線程:通過Thread類新建線程預設為前臺線程。當所有前臺線程關閉時,所有的後臺線程也會被直接終止,不會拋出異常。

3、掛起(Suspend)和喚醒(Resume):由於線程的執行順序和程式的執行情況不可預知,所以使用掛起和喚醒容易發生死鎖的情況,在實際應用中應該儘量少用。

4、阻塞線程:Join,阻塞調用線程,直到該線程終止。

5、終止線程:Abort:拋出 ThreadAbortException 異常讓線程終止,終止後的線程不可喚醒。Interrupt:拋出 ThreadInterruptException 異常讓線程終止,通過捕獲異常可以繼續執行。

6、線程優先順序:AboveNormal BelowNormal Highest Lowest Normal,預設為Normal。

二、線程的使用

線程函數通過委托傳遞,可以不帶參數,也可以帶參數(只能有一個參數),可以用一個類或結構體封裝參數。

 1 namespace Test
 2 {
 3     class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             Thread t1 = new Thread(new ThreadStart(TestMethod));
 8             Thread t2 = new Thread(new ParameterizedThreadStart(TestMethod));
 9             t1.IsBackground = true;
10             t2.IsBackground = true;
11             t1.Start();
12             t2.Start("hello");
13             Console.ReadKey();
14         }
15 
16         public static void TestMethod()
17         {
18             Console.WriteLine("不帶參數的線程函數");
19         }
20 
21         public static void TestMethod(object data)
22         {
23             string datastr = data as string;
24             Console.WriteLine("帶參數的線程函數,參數為:{0}", datastr);
25         }
26     } 
27 }

 

三、線程池

由於線程的創建和銷毀需要耗費一定的開銷,過多的使用線程會造成記憶體資源的浪費,出於對性能的考慮,於是引入了線程池的概念。線程池維護一個請求隊列,線程池的代碼從隊列提取任務,然後委派給線程池的一個線程執行,線程執行完不會被立即銷毀,這樣既可以在後臺執行任務,又可以減少線程創建和銷毀所帶來的開銷。

線程池線程預設為後臺線程(IsBackground)。

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             //將工作項加入到線程池隊列中,這裡可以傳遞一個線程參數
 6             ThreadPool.QueueUserWorkItem(TestMethod, "Hello");
 7             Console.ReadKey();
 8         }
 9 
10         public static void TestMethod(object data)
11         {
12             string datastr = data as string;
13             Console.WriteLine(datastr);
14         }
15     }

 

四、Task類

使用ThreadPool的QueueUserWorkItem()方法發起一次非同步的線程執行很簡單,但是該方法最大的問題是沒有一個內建的機制讓你知道操作什麼時候完成,有沒有一個內建的機制在操作完成後獲得一個返回值。為此,可以使用System.Threading.Tasks中的Task類。

構造一個Task<TResult>對象,併為泛型TResult參數傳遞一個操作的返回類型。

 1 class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Task<Int32> t = new Task<Int32>(n => Sum((Int32)n), 1000);
 6             t.Start();
 7             t.Wait();
 8             Console.WriteLine(t.Result);
 9             Console.ReadKey();
10         }
11 
12         private static Int32 Sum(Int32 n)
13         {
14             Int32 sum = 0;
15             for (; n > 0; --n)
16                 checked{ sum += n;} //結果太大,拋出異常
17             return sum;
18         }
19     }

 

一個任務完成時,自動啟動一個新任務。
一個任務完成後,它可以啟動另一個任務,下麵重寫了前面的代碼,不阻塞任何線程。

 1  class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Task<Int32> t = new Task<Int32>(n => Sum((Int32)n), 1000);
 6             t.Start();
 7             //t.Wait();
 8             Task cwt = t.ContinueWith(task => Console.WriteLine("The result is {0}",t.Result));
 9             Console.ReadKey();
10         }
11 
12         private static Int32 Sum(Int32 n)
13         {
14             Int32 sum = 0;
15             for (; n > 0; --n)
16                 checked{ sum += n;} //結果溢出,拋出異常
17             return sum;
18         }
19     }

 

五、委托非同步執行

委托的非同步調用:BeginInvoke() 和 EndInvoke()

 1  public delegate string MyDelegate(object data);
 2     class Program
 3     {
 4         static void Main(string[] args)
 5         {
 6             MyDelegate mydelegate = new MyDelegate(TestMethod);
 7             IAsyncResult result = mydelegate.BeginInvoke("Thread Param", TestCallback, "Callback Param");
 8 
 9             //非同步執行完成
10             string resultstr = mydelegate.EndInvoke(result);
11         }
12 
13         //線程函數
14         public static string TestMethod(object data)
15         {
16             string datastr = data as string;
17             return datastr;
18         }
19 
20         //非同步回調函數
21         public static void TestCallback(IAsyncResult data)
22         {
23             Console.WriteLine(data.AsyncState);
24         }
25     }

 

六、線程同步

  1)原子操作(Interlocked):幫助保護免受計劃程式切換上下文時某個線程正在更新可以由其他線程訪問的變數或者在單獨的處理器上同時執行兩個線程就可能出現的錯誤。 此類的成員不會引發異常。

 1 class Program
 2     {
 3         static int counter = 1;
 4 
 5         static void Main(string[] args)
 6         {
 7             Thread t1 = new Thread(new ThreadStart(F1));
 8             Thread t2 = new Thread(new ThreadStart(F2));
 9 
10             t1.Start();
11             t2.Start();
12 
13             t1.Join();
14             t2.Join();
15 
16             System.Console.ReadKey();
17         }
18 
19         static void F1()
20         {
21             for (int i = 0; i < 5; i++)
22             {
23                 Interlocked.Increment(ref counter);
24                 System.Console.WriteLine("Counter++ {0}", counter);
25                 Thread.Sleep(10);
26             }
27         }
28 
29         static void F2()
30         {
31             for (int i = 0; i < 5; i++)
32             {
33                 Interlocked.Decrement(ref counter);
34                 System.Console.WriteLine("Counter-- {0}", counter);
35                 Thread.Sleep(10);
36             }
37         }
38     }

 

  2)lock()語句:避免鎖定public類型,否則實例將超出代碼控制的範圍,定義private對象來鎖定。而自定義類推薦用私有的只讀靜態對象,比如:private static readonly object obj = new object();為什麼要設置成只讀的呢?這時因為如果在lock代碼段中改變obj的值,其它線程就暢通無阻了,因為互斥鎖的對象變了,object.ReferenceEquals必然返回false。Array 類型提供 SyncRoot。許多集合類型也提供 SyncRoot。

  3)Monitor實現線程同步

    通過Monitor.Enter() 和 Monitor.Exit()實現排它鎖的獲取和釋放,獲取之後獨占資源,不允許其他線程訪問。

    還有一個TryEnter方法,請求不到資源時不會阻塞等待,可以設置超時時間,獲取不到直接返回false。

 1         public void MonitorSomeThing()
 2         {
 3             try
 4             {
 5                 Monitor.Enter(obj);
 6                 dosomething();
 7             }
 8             catch(Exception ex)
 9             {
10                 
11             }
12             finally
13             {
14                 Monitor.Exit(obj);
15             }
16         }

 

  4)ReaderWriterLock

    當對資源操作讀多寫少的時候,為了提高資源的利用率,讓讀操作鎖為共用鎖,多個線程可以併發讀取資源,而寫操作為獨占鎖,只允許一個線程操作。

  1 class SynchronizedCache  
  2     {  
  3         private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();  
  4         private Dictionary<int, string> innerCache = new Dictionary<int, string>();  
  5   
  6         public string Read(int key)  
  7         {  
  8             cacheLock.EnterReadLock();  
  9             try  
 10             {  
 11                 return innerCache[key];  
 12             }  
 13             finally  
 14             {  
 15                 cacheLock.ExitReaderLock();  
 16             }  
 17         }  
 18   
 19         public void Add(int key, string value)  
 20         {  
 21             cacheLock.EnterWriteLock();  
 22             try  
 23             {  
 24                 innerCache.Add(key, value);  
 25             }  
 26             finally  
 27             {  
 28                 cacheLock.ExitWriteLock();  
 29             }  
 30         }  
 31   
 32         public bool AddWithTimeout(int key, string value, int timeout)  
 33         {  
 34             if (cacheLock.TryEnterWriteLock(timeout))  
 35             {  
 36                 try  
 37                 {  
 38                     innerCache.Add(key, value);  
 39                 }  
 40                 finally  
 41                 {  
 42                     cacheLock.ExitReaderLock();  
 43                 }  
 44                 return true;  
 45             }  
 46             else  
 47             {  
 48                 return false;  
 49             }  
 50         }  
 51   
 52         public AddOrUpdateStatus AddOrUpdate(int key, string value)  
 53         {  
 54             cacheLock.EnterUpgradeableReadLock();  
 55             try  
 56             {  
 57                 string result = null;  
 58                 if (innerCache.TryGetValue(key, out result))  
 59                 {  
 60                     if (result == value)  
 61                     {  
 62                         return AddOrUpdateStatus.Unchanged;  
 63                     }  
 64                     else  
 65                     {  
 66                         cacheLock.EnterWriteLock();  
 67                         try  
 68                         {  
 69                             innerCache[key] = value;  
 70                         }  
 71                         finally  
 72                         {  
 73                             cacheLock.ExitWriteLock();  
 74                         }  
 75                         return AddOrUpdateStatus.Updated;  
 76                     }  
 77                 }  
 78                 else  
 79                 {  
 80                     cacheLock.EnterWriteLock();  
 81                     try  
 82                     {  
 83                         innerCache.Add(key, value);  
 84                     }  
 85                     finally  
 86                     {  
 87                         cacheLock.ExitWriteLock();  
 88                     }  
 89                     return AddOrUpdateStatus.Added;  
 90                 }  
 91             }  
 92             finally  
 93             {  
 94                 cacheLock.ExitUpgradeableReadLock();  
 95             }  
 96         }  
 97   
 98         public void Delete(int key)  
 99         {  
100             cacheLock.EnterWriteLock();  
101             try  
102             {  
103                 innerCache.Remove(key);  
104             }  
105             finally  
106             {  
107                 cacheLock.ExitWriteLock();  
108             }  
109         }  
110   
111         public enum AddOrUpdateStatus  
112         {  
113             Added,  
114             Updated,  
115             Unchanged  
116         };  
117     }

 

  5)事件(Event)類實現同步

    事件類有兩種狀態,終止狀態和非終止狀態,終止狀態時調用WaitOne可以請求成功,通過Set將時間狀態設置為終止狀態。

    1)AutoResetEvent(自動重置事件)

    2)ManualResetEvent(手動重置事件)

              AutoResetEvent和ManualResetEvent這兩個類經常用到, 他們的用法很類似,但也有區別。Set方法將信號置為發送狀態,Reset方法將信號置為不發送狀態,WaitOne等待信號的發送。可以通過構造函數的參數值來決定其初始狀態,若為true則非阻塞狀態,為false為阻塞狀態。如果某個線程調用WaitOne方法,則當信號處於發送狀態時,該線程會得到信號, 繼續向下執行。其區別就在調用後,AutoResetEvent.WaitOne()每次只允許一個線程進入,當某個線程得到信號後,AutoResetEvent會自動又將信號置為不發送狀態,則其他調用WaitOne的線程只有繼續等待.也就是說,AutoResetEvent一次只喚醒一個線程;而ManualResetEvent則可以喚醒多個線程,因為當某個線程調用了ManualResetEvent.Set()方法後,其他調用WaitOne的線程獲得信號得以繼續執行,而ManualResetEvent不會自動將信號置為不發送。也就是說,除非手工調用了ManualResetEvent.Reset()方法,則ManualResetEvent將一直保持有信號狀態,ManualResetEvent也就可以同時喚醒多個線程繼續執行。

  6)信號量(Semaphore)

      信號量是由內核對象維護的int變數,為0時,線程阻塞,大於0時解除阻塞,當一個信號量上的等待線程解除阻塞後,信號量計數+1。

      線程通過WaitOne將信號量減1,通過Release將信號量加1,使用很簡單。

 1         public Thread thrd;
 2         //創建一個可授權2個許可證的信號量,且初始值為2
 3         static Semaphore sem = new Semaphore(2, 2);
 4  
 5         public mythread(string name)
 6         {
 7             thrd = new Thread(this.run);
 8             thrd.Name = name;
 9             thrd.Start();
10         }
11         void run()
12         {
13             Console.WriteLine(thrd.Name + "正在等待一個許可證……");
14             //申請一個許可證
15             sem.WaitOne();
16             Console.WriteLine(thrd.Name + "申請到許可證……");
17             for (int i = 0; i < 4 ; i++)
18             {
19                 Console.WriteLine(thrd.Name + "" + i);
20                 Thread.Sleep(1000);
21             }
22             Console.WriteLine(thrd.Name + " 釋放許可證……");
23             //釋放
24             sem.Release();
25         }
26     }
27  
28     class mysemaphore
29     {
30         public static void Main()
31         {
32             mythread mythrd1 = new mythread("Thrd #1");
33             mythread mythrd2 = new mythread("Thrd #2");
34             mythread mythrd3 = new mythread("Thrd #3");
35             mythread mythrd4 = new mythread("Thrd #4");
36             mythrd1.thrd.Join();
37             mythrd2.thrd.Join();
38             mythrd3.thrd.Join();
39             mythrd4.thrd.Join();
40         }
41     } 

 

  7)互斥體(Mutex)

      獨占資源,可以把Mutex看作一個計程車,乘客看作線程。乘客首先等車,然後上車,最後下車。當一個乘客在車上時,其他乘客就只有等他下車以後才可以上車。而線程與C# Mutex對象的關係也正是如此,線程使用Mutex.WaitOne()方法等待C# Mutex對象被釋放,如果它等待的C# Mutex對象被釋放了,它就自動擁有這個對象,直到它調用Mutex.ReleaseMutex()方法釋放這個對象,而在此期間,其他想要獲取這個C# Mutex對象的線程都只有等待。

 1 class Test
 2     {
 3         /// <summary>
 4         /// 應用程式的主入口點。
 5         /// </summary>
 6         [STAThread]
 7         static void Main(string[] args)
 8         {
 9             bool flag = false;
10             System.Threading.Mutex mutex = new System.Threading.Mutex(true, "Test", out flag);
11             //第一個參數:true--給調用線程賦予互斥體的初始所屬權
12             //第一個參數:互斥體的名稱
13             //第三個參數:返回值,如果調用線程已被授予互斥體的初始所屬權,則返回true
14             if (flag)
15             {
16                 Console.Write("Running");
17             }
18             else
19             {
20                 Console.Write("Another is Running");
21                 System.Threading.Thread.Sleep(5000);//線程掛起5秒鐘
22                 Environment.Exit(1);//退出程式
23             }
24             Console.ReadLine();
25         }
26     }

 

   8)跨進程間的同步

      通過設置同步對象的名稱就可以實現系統級的同步,不同應用程式通過同步對象的名稱識別不同同步對象。

 1  static void Main(string[] args)
 2         {
 3             string MutexName = "InterProcessSyncName";
 4             Mutex SyncNamed;     //聲明一個已命名的互斥對象
 5              try
 6             {
 7                 SyncNamed = Mutex.OpenExisting(MutexName);       //如果此命名互斥對象已存在則請求打開
 8             }
 9             catch (WaitHandleCannotBeOpenedException)
10             {
11                 SyncNamed = new Mutex(false, MutexName);         //如果初次運行沒有已命名的互斥對象則創建一個
12             }
13             Task MulTesk = new Task
14                 (
15                     () =>                  //多任務並行計算中的匿名方法,用委托也可以
16                     {
17                         for (; ; )         //為了效果明顯而設計
18                         {
19                             Console.WriteLine("當前進程等待獲取互斥訪問權......");
20                             SyncNamed.WaitOne();
21                             Console.WriteLine("獲取互斥訪問權,訪問資源完畢,按回車釋放互斥資料訪問權.");
22                             Console.ReadLine();
23                             SyncNamed.ReleaseMutex();
24                             Console.WriteLine("已釋放互斥訪問權。");
25                         }
26                     }
27                 );
28             MulTesk.Start();
29             MulTesk.Wait();
30         }

   9)分散式的同步

  可以使用redis任務隊列或者redis相關特性

 1                     Parallel.For(0, 1000000, i =>
 2                     {
 3                         Stopwatch sw1 = new Stopwatch();
 4                         sw1.Start();
 5 
 6                         if (redisHelper.GetRedisOperation().Lock(key))
 7                         {
 8                             var tt = int.Parse(redisHelper.GetRedisOperation().StringGet("calc"));
 9 
10                             tt++;
11 
12                             redisHelper.GetRedisOperation().StringSet("calc", tt.ToString());
13 
14                             redisHelper.GetRedisOperation().UnLock(key);
15                         }
16                         var v = sw1.ElapsedMilliseconds;
17                         if (v >= 10 * 1000)
18                         {
19                             Console.Write("f");
20                         }
21                         sw1.Stop();
22                     });

 

 

 


轉載請標明本文來源:http://www.cnblogs.com/yswenli/p/7421475.html 
更多內容歡迎star作者的github:https://github.com/yswenli/
如果發現本文有什麼問題和任何建議,也隨時歡迎交流~

 


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

-Advertisement-
Play Games
更多相關文章
  • 為什麼有這種需求, 原因是這樣的, 公司有一個Java的web項目,在另一臺伺服器A上,最近老闆一時興起,想把他合併到這台穩定點的伺服器B上,伺服器B上使用IIS來寄宿asp.net 網站, 怎麼辦呢,硬著頭皮上吧,在網上找各種解決方案: 解決方案一:isapi_redirect 這個方法按照方法試 ...
  • 在網路編程過程中需要向伺服器上傳文件。 Multipart/form-data是上傳文件的一種方式。 ...
  • 創建和開發ASP.NET Core應用可以有二種方式:最簡單的方式是通過visual studio 2017 來創建,其優點是簡單方便,但需要安裝最新版本visual studio 2017 preview 15.3 。另一種方式是使用visual studio code來創建,vscode 則是一... ...
  • 微信分享代碼,先引入: 獲取簽名: 分享代碼: wxsign輸出的json ...
  • 1、導出Excel : 首先引用NPOI包(Action一定要用FileResult) /// <summary> /// 批量導出需要導出的列表 /// </summary> /// <returns></returns> public FileResult ExportStu2() { //獲取 ...
  • ASP.NET Core 是新一代的 ASP.NET,第一次出現時代號為 ASP.NET vNext,後來命名為ASP.NET 5,隨著它的完善與成熟,最終命名為 ASP.NET Core,表明它不是 ASP.NET 的升級,而是一個重新設計的Web開發框架。而它一個非常重要的變化就是它不再依賴於I ...
  • 基於業務組件模型的工作流模塊設計 摘要 當前基於 BPLE 的業務流程管理(BPM)以及基於 XPDL 的工作流(WF)都有成熟的理論和相應的產品支持,特別是在國內,工作流(WF)的應用十分廣泛。本文從流程入手,結合業務流程管理、工作流、績效管理、個人門戶等概念,將業務流程管理和工作流結合起來 ,搭 ...
  • 通過使用匿名委托(匿名方法),使編程變得更加靈活,有關委托與匿名委托請參考我的前一篇Blog《委托與匿名委托》。 繼續之前示例,代碼如下: 上述程式worker將按照Main給定的參數與計算方式(method),計算出結果返回。根據等效性代碼可以進一步簡化,如下: 看到此處有過js、jquery開發 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...