非同步編程(async&await)

来源:https://www.cnblogs.com/jonins/archive/2018/09/12/9558275.html
-Advertisement-
Play Games

前言 本來這篇文章上個月就該發佈了,但是因為忙 QuarkDoc 一直沒有時間整理,所以耽擱到今天,現在回歸正軌。 C# 5.0 雖然只引入了2個新關鍵詞:async和await。然而它大大簡化了非同步方法的編程。 在 線程池(threadPool)大致介紹了微軟在不同時期使用的不同的非同步模式,有3種 ...


 前言

本來這篇文章上個月就該發佈了,但是因為忙 QuarkDoc  一直沒有時間整理,所以耽擱到今天,現在回歸正軌。

C# 5.0 雖然只引入了2個新關鍵詞:asyncawait。然而它大大簡化了非同步方法的編程。

在 線程池(threadPool)大致介紹了微軟在不同時期使用的不同的非同步模式,有3種:

1.非同步模式

2.基於事件的非同步模式

3.基於任務的非同步模式(TAP)

而最後一種就是利用asyncawait關鍵字來實現的(TAP是現在微軟極力推崇的一種非同步編程方式)。

但請謹記,asyncawait關鍵字只是編譯器功能。編譯器會用Task類創建代碼。如果不使用這兩個關鍵詞,用C#4.0的Task類同樣可以實現相同的功能,只是沒有那麼方便而已。

 

認識asyncawait

使用asyncawait關鍵詞編寫非同步代碼,具有與同步代碼相當的結構和簡單性,並且摒棄了非同步編程的複雜結構。

但是在理解上剛開始會很不習慣,而且會把一些情況想當然了,而真實情況會相去甚遠(我犯過這樣的錯誤)。所以根據幾個示例一步步理解更加的靠譜些。

1.一個簡單的同步方法

這是一個簡單的同步方法調用示例:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Console.WriteLine($"頭部已執行,當前主線程Id為:{Thread.CurrentThread.ManagedThreadId}");
 6             string result = SayHi("jack");
 7             Console.WriteLine(result);
 8             Console.WriteLine($"尾部已執行,當前主線程Id為:{Thread.CurrentThread.ManagedThreadId}");
 9             Console.ReadKey();
10         }
11         static string SayHi(string name)
12         {
13             Task.Delay(2000).Wait();//非同步等待2s
14             Console.WriteLine($"SayHi執行,當前線程Id為:{Thread.CurrentThread.ManagedThreadId}");
15             return $"Hello,{name}";
16         }
17     }

執行結果如下,方法在主線程中運行,主線程被阻塞。

2.同步方法非同步化

示例將方法放到任務內執行:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Console.WriteLine($"頭部已執行,當前主線程Id為:{Thread.CurrentThread.ManagedThreadId}");
 6             string result = SayHiAsync("jack").Result;
 7             Console.WriteLine(result);
 8             Console.WriteLine($"尾部已執行,當前主線程Id為:{Thread.CurrentThread.ManagedThreadId}");
 9             Console.ReadKey();
10         }
11         static Task<string> SayHiAsync(string name)
12         {
13             return Task.Run<string>(() => { return SayHi(name); });
14         }
15         static string SayHi(string name)
16         {
17             Task.Delay(2000).Wait();//非同步等待2s
18             Console.WriteLine($"SayHi執行,當前線程Id為:{Thread.CurrentThread.ManagedThreadId}");
19             return $"Hello,{name}";
20         }
21     }

執行結果如下,方法在另外一個線程中運行,因為主線程調用了ResultResult在任務沒有完成時內部會使用Wait,所以主線程還是會被阻塞。

3.延續任務

示例為了避免阻塞主線程使用任務延續的方式:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Console.WriteLine($"頭部已執行,當前主線程Id為:{Thread.CurrentThread.ManagedThreadId}");
 6             Task<string> task = SayHiAsync("jack");
 7             task.ContinueWith(t =>//延續任務,指定任務執行完成後延續的操作
 8             {
 9                 Console.WriteLine($"延續執行,當前線程Id為:{Thread.CurrentThread.ManagedThreadId}");
10                 string result = t.Result;
11                 Console.WriteLine(result);
12             });
13             Console.WriteLine($"尾部已執行,當前主線程Id為:{Thread.CurrentThread.ManagedThreadId}");
14             Console.ReadKey();
15         }
16         static Task<string> SayHiAsync(string name)
17         {
18             return Task.Run<string>(() => { return SayHi(name); });
19         }
20         static string SayHi(string name)
21         {
22             Task.Delay(2000).Wait();//非同步等待2s
23             Console.WriteLine($"SayHi執行,當前線程Id為:{Thread.CurrentThread.ManagedThreadId}");
24             return $"Hello,{name}";
25         }
26     }

執行結果如下,方法在另外一個線程中運行,因為任務附加了延續,延續會在任務完成後處理返回值,而主線程不會被阻塞。這應該就是想要的效果了。

 

4.使用async和await構建非同步方法調用

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Console.WriteLine($"頭部已執行,當前主線程Id為:{Thread.CurrentThread.ManagedThreadId}");
 6             CallerWithAsync("jack");
 7             Console.WriteLine($"尾部已執行,當前主線程Id為:{Thread.CurrentThread.ManagedThreadId}");
 8             Console.ReadKey();
 9         }
10         async static void CallerWithAsync(string name)
11         {
12             Console.WriteLine($"非同步調用頭部執行,當前線程Id為:{Thread.CurrentThread.ManagedThreadId}");
13             string result = await SayHiAsync(name);
14             Console.WriteLine($"非同步調用尾部執行,當前線程Id為:{Thread.CurrentThread.ManagedThreadId}");
15             Console.WriteLine(result);
16         }
17         static Task<string> SayHiAsync(string name)
18         {
19             return Task.Run<string>(() => { return SayHi(name); });
20         }
21         static string SayHi(string name)
22         {
23             Task.Delay(2000).Wait();//非同步等待2s
24             Console.WriteLine($"SayHi執行,當前線程Id為:{Thread.CurrentThread.ManagedThreadId}");
25             return $"Hello,{name}";
26         }
27     }

執行結果如下,使用await關鍵字來調用返回任務的非同步方法SayHiAsync,而使用await需要有用async修飾符聲明的方法,在SayHiAsync方法為完成前,下麵的方法不會繼續執行。但是主線程並沒有阻塞,且任務處理完成後await後的邏輯繼續執行。

本質:編譯器將await關鍵字後的所有代碼放進了延續(ContinueWith)方法的代碼塊中來轉換await關鍵詞。

 

解析asyncawait

1.非同步(async)

使用async修飾符標記的方法稱為非同步方法,非同步方法只可以具有以下返回類型:
1.Task
2.Task<TResult>
3.void
4.從C# 7.0開始,任何具有可訪問的GetAwaiter方法的類型System.Threading.Tasks.ValueTask<TResult> 類型屬於此類實現(需向項目添加System.Threading.Tasks.Extensions NuGet 包)。

非同步方法通常包含 await 運算符的一個或多個實例,但缺少 await 表達式也不會導致生成編譯器錯誤。 如果非同步方法未使用 await 運算符標記暫停點,那麼非同步方法會作為同步方法執行,即使有 async 修飾符也不例外,編譯器將為此類方法發佈一個警告。

2.等待(await)

await 表達式只能在由 async 修飾符標記的封閉方法體lambda 表達式或非同步方法中出現。在其他位置,它會解釋為標識符。

使用await運算符的任務只可用於返回 TaskTask<TResult> System.Threading.Tasks.ValueType<TResult> 對象的方法。

非同步方法同步運行,直至到達其第一個 await 表達式,此時await在方法的執行中插入掛起點,會將方法掛起直到所等待的任務完成,然後繼續執行await後面的代碼區域。

await 表達式並不阻止正在執行它的線程。 而是使編譯器將剩下的非同步方法註冊為等待任務的延續任務。 控制權隨後會返回給非同步方法的調用方。 任務完成時,它會調用其延續任務,非同步方法的執行會在暫停的位置處恢復。

註意:

1.無法等待具有 void 返回類型的非同步方法,並且無效返回方法的調用方捕獲不到非同步方法拋出的任何異常

2.非同步方法無法聲明 inrefout 參數,但可以調用包含此類參數的方法。 同樣,非同步方法無法通過引用返回值,但可以調用包含 ref 返回值的方法。

 

非同步方法運行機理(控制流)

非同步編程中最需弄清的是控制流是如何從方法移動到方法的。

下列示例及說明引自(官方文檔),個人認為已經很清晰了:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             var result = AccessTheWebAsync();
 6             Console.ReadKey();
 7         }
 8         async static Task<int> AccessTheWebAsync()
 9         {
10             HttpClient client = new HttpClient();
11             // GetStringAsync返回一個任務。任務Result會得到一個字元串(urlContents)。
12             Task<string> getStringTask = client.GetStringAsync("https://www.cnblogs.com/jonins/");
13             //您可以在這裡完成不依賴於GetStringAsync的字元串的工作。
14             DoIndependentWork();
15             //等待的操作員暫停進入WebAsync。
16             //AccessTheWebAsync在getStringTask完成之前不能繼續。
17             //同時,控制權返回到AccessTheWebAsync的調用方。
18             //當getStringTask完成後,控制項權將繼續在這裡工作。 然後,await運算符從getStringTask檢索字元串結果。 
19             string urlContents = await getStringTask;
20             //任務完成
21             Console.WriteLine(urlContents.Length);
22             //return語句指定一個整數結果。 
23             return urlContents.Length;
24         }
25         static void DoIndependentWork()
26         {
27             Console.WriteLine("Working..........");
28         }
29     }

 

多個非同步方法

在一個非同步方法里,可以調用一個或多個非同步方法,如何編碼取決於非同步方法間結果是否相互依賴

1.順序調用非同步方法

使用await關鍵詞可以調用每個非同步方法,如果一個非同步方法需要使用另一個非同步方法的結果,await關鍵詞就非常必要。

示例如下:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Console.WriteLine("執行前.....");
 6             GetResultAsync();
 7             Console.WriteLine("執行中.....");
 8             Console.ReadKey();
 9         }
10         async static void GetResultAsync()
11         {
12             var number1 = await GetResult(10);
13             var number2 =  GetResult(number1);
14             Console.WriteLine($"結果分別為:{number1}和{number2.Result}");
15         }
16         static Task<int> GetResult(int number)
17         {
18             return Task.Run<int>(() => { Task.Delay(1000).Wait(); return number + 10; });
19         }
20     }

2.使用組合器

如果非同步方法間相互不依賴,則每個非同步方法都不使用await,而是把每個非同步方法的結果賦值給Task變數,就會運行得更快。

示例如下:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Console.WriteLine("執行前.....");
 6             GetResultAsync();
 7             Console.WriteLine("執行中.....");
 8             Console.ReadKey();
 9         }
10         async static void GetResultAsync()
11         {
12             Task<int> task1 = GetResult(10);
13             Task<int> task2 = GetResult(20);
14             await Task.WhenAll(task1, task2);
15             Console.WriteLine($"結果分別為:{task1.Result}和{task2.Result}");
16         }
17         static Task<int> GetResult(int number)
18         {
19             return Task.Run<int>(() => { Task.Delay(1000).Wait(); return number + 10; });
20         }
21     }

Task類定於2個組合器分別為:WhenAllWhenAny

WhenAll是在所有傳入的任務都完成時才返回Task

WhenAny是在傳入的任務其中一個完成就會返回Task

 

非同步方法的異常處理

1.異常處理

以下示例一種是普通的錯誤的捕獲方式,另一種是非同步方法異常捕獲方式:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5 
 6             DontHandle();
 7             HandleError();
 8             Console.ReadKey();
 9         }
10         //錯誤處理
11         static void DontHandle()
12         {
13             try
14             {
15                 var task = ThrowAfter(0, "DontHandle Error");
16             }
17             catch (Exception ex)
18             {
19 
20                 Console.WriteLine(ex.Message);
21             }
22         }
23         //非同步方法錯誤處理
24         static async void HandleError()
25         {
26             try
27             {
28                 await ThrowAfter(2000, "HandleError Error");
29             }
30             catch (Exception ex)
31             {
32 
33                 Console.WriteLine(ex.Message);
34             }
35         }
36         //在延遲後拋出異常
37         static async Task ThrowAfter(int ms, string message)
38         {
39             await Task.Delay(ms);
40             throw new Exception(message);
41         }
42     }

執行結果如下:

調用非同步方法,如果只是簡單的放在try/catch塊中,將會捕獲不到異常這是因為DontHandle方法在ThrowAfter拋出異常之前已經執行完畢(返回void的非同步方法不會等待。這是因為從async void方法拋出的異常無法捕獲。因此非同步方法最好返回一個Task類型)。

非同步方法的一個較好異常處理方式是使用await關鍵字,將其放在try/catch

2.多個非同步方法異常處理

如果調用了多個非同步方法,在第一個非同步方法拋出異常,後續的方法將不會被調用,catch塊內只會處理出現的第一個異常。

所以正確的做法是使用Task.WhenAll,不管任務是否拋出異常都會等到所有任務完成。Task.WhenAll結束後,異常被catch語句捕獲到。如果只是捕獲Exception,我們只能看到WhenAll方法的第一個發生異常的任務信息,不會拋出後續的異常任務

如果要捕獲所有任務的異常信息,就是對任務聲明變數,在catch塊內可以訪問,再使用IsFaulted屬性檢查任務的狀態,以確認它們是否出現錯誤,然後再進行處理。示例如下:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             HandleError();
 6             Console.ReadKey();
 7         }
 8         //正確的處理方式
 9         static async void HandleError()
10         {
11             Task t1 = null;
12             Task t2 = null;
13             try
14             {
15                 t1 = ThrowAfter(1000, "HandleError-One-Error");
16                 t2 = ThrowAfter(2000, "HandleError-Two-Error");
17                 await Task.WhenAll(t1, t2);
18             }
19             catch (Exception)
20             {
21                 if (t1.IsFaulted)
22                     Console.WriteLine(t1.Exception.InnerException.Message);
23                 if (t2.IsFaulted)
24                     Console.WriteLine(t2.Exception.InnerException.Message);
25             }
26         }
27         //在延遲後拋出異常
28         static async Task ThrowAfter(int ms, string message)
29         {
30             await Task.Delay(ms);
31             throw new Exception(message);
32         }
33     }

3.使用AggregateException捕獲非同步方法異常

 在 任務(task)中介紹過AggregateException,它包含了等待中所有異常的列表,可輕鬆遍歷處理所有異常信息。示例如下:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             HandleError();
 6             Console.ReadKey();
 7         }
 8         //正確的處理方式
 9         static async void HandleError()
10         {
11             Task taskResult = null;
12             try
13             {
14                 Task t1 = ThrowAfter(1000, "HandleError-One-Error");
15                 Task t2 = ThrowAfter(2000, "HandleError-Two-Error");
16                 await (taskResult = Task.WhenAll(t1, t2));
17             }
18             catch (Exception)
19             {
20                 foreach (var ex in taskResult.Exception.InnerExceptions)
21                 {
22                     Console.WriteLine(ex.Message);
23                 }
24           
25             }
26         }
27         //在延遲後拋出異常
28         static async Task ThrowAfter(int ms, string message)
29         {
30             await Task.Delay(ms);
31             throw new Exception(message);
32         }
33     }

 

重要的補充與建議

1.提高響應能力

.NET有很多非同步API我們都可以通過async/await構建調用提高響應能力,例如:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Demo();
 6             Console.ReadKey();
 7         }
 8         static async void Demo()
 9         {
10             HttpClient httpClient = new HttpClient();
11             var getTaskResult = await httpClient.GetStringAsync("https://www.cnblogs.com/jonins/");
12             Console.WriteLine(getTaskResult);
13         }
14     }

這些API都有相同原則即以Async結尾。

 

2.重要建議

1.async方法需在其主體中具有await 關鍵字,否則它們將永不暫停。同時C# 編譯器將生成一個警告,此代碼將會以類似普通方法的方式進行編譯和運行。 請註意這會導致效率低下,因為由 C# 編譯器為非同步方法生成的狀態機將不會完成任何任務。

2.應將“Async”作為尾碼添加到所編寫的每個非同步方法名稱中。這是 .NET 中的慣例,以便更輕鬆區分同步和非同步方法。

3.async void 應僅用於事件處理程式。因為事件不具有返回類型(因此無法返回 TaskTask<T>)。 其他任何對 async void 的使用都不遵循 TAP 模型,且可能存在一定使用難度。

例如:async void 方法中引發的異常無法在該方法外部被捕獲或十分難以測試 async void 方法。

3.以非阻止方式處理等待任務

 

非同步編程準則

非同步編程的準則確定所需執行的操作是I/O-Bound還是 CPU-Bound。因為這會極大影響代碼性能,並可能導致某些構造的誤用。

考慮兩個問題:

1.你的代碼是否會“等待”某些內容,例如資料庫中的數據或web資源等?如果答案為“是”,則你的工作是 I/O-Bound

2.你的代碼是否要執行開銷巨大的計算?如果答案為“是”,則你的工作是 CPU-Bound

如果你的工作為 I/O-Bound,請使用 async await(而不使用 Task.Run)。 不應使用任務並行庫。 
如果你的工作為 CPU-Bound,並且你重視響應能力,請使用 async await,併在另一個線程上使用 Task.Run 生成工作。 如果該工作同時適用於併發和並行,則應考慮使用任務並行庫。

 

結語

如果想要瞭解狀態機請戳:這裡 。

 

參考資料

C#高級編程(第10版) C# 6 & .NET Core 1.0   Christian Nagel  

果殼中的C# C#5.0權威指南  Joseph Albahari

https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/async/index

https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/async

https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/await

 


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

-Advertisement-
Play Games
更多相關文章
  • 前言 在 "上一篇" 中我們學習了結構型模式的外觀模式和裝飾器模式。本篇則來學習下組合模式和過濾器模式。 組合模式 簡介 組合模式是用於把一組相似的對象當作一個單一的對象。組合模式依據樹形結構來組合對象,用來表示部分以及整體層次。這種類型的設計模式屬於結構型模式,它創建了對象組的樹形結構。 簡單來說 ...
  • 最近由於項目需要,在研究打壓測試工具,以及當測試連接過多後端伺服器配置問題 測試工具選用locust,locust中文意思為蝗蟲,可以想象,locust就像成片的蝗蟲,撲向我們的服務。 它支持分散式的打壓測試,每個實例可自定義執行任務,執行任務可用python腳本實現,具體如何寫python腳本這裡 ...
  • 監聽器的分類 監聽域對象自身創建和銷毀的監聽器: ①ServletContextListener介面 監聽 SercvletContext對象 ②HttpSessionListener介面 監聽 HttpSession對象 ③ServletRequestListener介面 監聽 ServletRe ...
  • 題意 題目鏈接 為了固定S**p*鴿鴿,whx和zzt來到鴿具商店選購鴿子固定器。 鴿具商店有 nn 個不同大小的固定器,現在可以選擇至多 mm 個來固定S**p*鴿鴿。每個固定器有大小 sisi 和牢固程度 vivi。 如果他們選購的固定器大小不一或是不牢固,固定S**p*鴿鴿的時候肯定會很頭疼, ...
  • 數組轉List 需要註意的是, Arrays.asList() 返回一個受指定數組決定的固定大小的列表。所以不能做 add 、 remove 等操作,否則會報錯。 List staffsList = Arrays.asList(staffs); staffsList.add("Mary"); // ...
  • 本次抓取貓眼電影Top100榜所用到的知識點: 1. python requests庫 2. 正則表達式 3. csv模塊 4. 多進程 正文 目標站點分析 通過對目標站點的分析, 來確定網頁結構, 進一步確定具體的抓取方式. 1. 瀏覽器打開貓眼電影首頁, 點擊"榜單", 點擊"Top100榜", ...
  • tomcat實現熱部署的配置 我們實現熱部署後,自然就可以通過maven操作tomcat了,所以就需要maven取得操作tomcat的許可權,現在這一步就是配置tomcat的可操作許可權. 進入tomcat安裝目錄 #進入tomcat安裝目錄 cd /usr/local/devlop/tomcat #進 ...
  • 驗證碼作用: 沒有驗證碼登陸,黑客會更加容易破解你的賬號,通過組合碼刷機等黑客技術來破取你的密碼,有了驗證碼相當於加了一層很厚的屏障,安全繫數很高。 驗證碼是一種區分用戶是電腦和人的公共全自動程式。 驗證碼作用:可以防止惡意破解密碼、刷票、論壇灌水,有效防止某個黑客對某一個特定註冊用戶用特定程式暴 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...