async_await 源碼分析

来源:https://www.cnblogs.com/ccwzl/archive/2023/08/16/17636189.html
-Advertisement-
Play Games

# UGUI的InputField(輸入框)組件的介紹及使用 ## 1. 什麼是UGUI的InputField組件? UGUI的InputField組件是Unity中的一個用戶界面組件,用於接收用戶的輸入。它可以用於創建文本輸入框、密碼輸入框等功能。 ## 2. UGUI的InputField組件的 ...


async/await 源碼解析

這篇文章主要是分析 async/await 這個語法糖,分析一下 async 和 await 是如何做到非同步的。首先,我先拋出兩個問題,各位可以先想一下。

  1. await 之後的方法是何時執行,如何執行的?
  2. 為什麼 await 之後的代碼會在不同的線程執行?

demo

要想知道 async/await 是怎麼運行的,需要先寫一個demo,然後進行一下反編譯,就可以得到 async/await 編譯後的代碼,然後就可以開始分析了。
下麵是簡單使用 async/await 的demo:

static async Task Main(string[] args)
{
       Console.WriteLine("1"+Thread.CurrentThread.ManagedThreadId.ToString());
       await GetThreadID2();
       Console.WriteLine("2"+Thread.CurrentThread.ManagedThreadId.ToString());
}
public async static Task GetThreadID2()
{
       Console.WriteLine("3"+Thread.CurrentThread.ManagedThreadId.ToString());
       await Task.Run(() => Console.WriteLine("4"+Thread.CurrentThread.ManagedThreadId.ToString()));          
       Console.WriteLine("5"+Thread.CurrentThread.ManagedThreadId.ToString());
}

然後我們來反編譯一下這段代碼(可以使用 dnSpy 進行反編譯):

namespace AsyncTest
{
    internal class Program
    {
        [DebuggerStepThrough]
        private static Task Main(string[] args)
        {
            Program.<Main>d__0 <Main>d__ = new Program.<Main>d__0();
            <Main>d__.<>t__builder = AsyncTaskMethodBuilder.Create();
            <Main>d__.args = args;
            <Main>d__.<>1__state = -1;
            <Main>d__.<>t__builder.Start<Program.<Main>d__0>(ref <Main>d__);
            return <Main>d__.<>t__builder.Task;
        }
        [DebuggerStepThrough]
        public static Task GetThreadID2()
        {
            Program.<GetThreadID2>d__2 <GetThreadID2>d__ = new Program.<GetThreadID2>d__2();
            <GetThreadID2>d__.<>t__builder = AsyncTaskMethodBuilder.Create();
            <GetThreadID2>d__.<>1__state = -1;
            <GetThreadID2>d__.<>t__builder.Start<Program.<GetThreadID2>d__2>(ref <GetThreadID2>d__);
            return <GetThreadID2>d__.<>t__builder.Task;
        }

        [DebuggerStepThrough]
        private static void <Main>(string[] args)
        {
            Program.Main(args).GetAwaiter().GetResult();
        }
        [CompilerGenerated]
        private sealed class <Main>d__0 : IAsyncStateMachine
        {
            void IAsyncStateMachine.MoveNext()
            {
                int num = this.<>1__state;
                try
                {
                    TaskAwaiter awaiter;
                    if (num != 0)
                    {
                        Console.WriteLine("1" + Thread.CurrentThread.ManagedThreadId.ToString());
                        awaiter = Program.GetThreadID2().GetAwaiter();
                        if (!awaiter.IsCompleted)
                        {
                            this.<>1__state = 0;
                            this.<>u__1 = awaiter;
                            Program.<Main>d__0 <Main>d__ = this;
                            this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Program.<Main>d__0>(ref awaiter, ref <Main>d__);
                            return;
                        }
                    }
                    else
                    {
                        awaiter = this.<>u__1;
                        this.<>u__1 = default(TaskAwaiter);
                        this.<>1__state = -1;
                    }
                    awaiter.GetResult();
                    Console.WriteLine("2" + Thread.CurrentThread.ManagedThreadId.ToString());
                    Console.ReadLine();
                }
                catch (Exception exception)
                {
                    this.<>1__state = -2;
                    this.<>t__builder.SetException(exception);
                    return;
                }
                this.<>1__state = -2;
                this.<>t__builder.SetResult();
            }
        }
        [CompilerGenerated]
        [Serializable]
        private sealed class <>c
        {
            internal void <GetThreadID2>b__2_0()
            {
                Console.WriteLine("4" + Thread.CurrentThread.ManagedThreadId.ToString());
            }
            public static readonly Program.<>c <>9 = new Program.<>c();

            public static Func<string> <>9__1_0;

            public static Action <>9__2_0;
        }
        [CompilerGenerated]
        private sealed class <GetThreadID2>d__2 : IAsyncStateMachine
        {
            void IAsyncStateMachine.MoveNext()
            {
                int num = this.<>1__state;
                try
                {
                    TaskAwaiter awaiter;
                    if (num != 0)
                    {
                        Console.WriteLine("3" + Thread.CurrentThread.ManagedThreadId.ToString());
                        awaiter = Task.Run(new Action(Program.<>c.<>9.<GetThreadID2>b__2_0)).GetAwaiter();
                        if (!awaiter.IsCompleted)
                        {
                            this.<>1__state = 0;
                            this.<>u__1 = awaiter;
                            Program.<GetThreadID2>d__2 <GetThreadID2>d__ = this;
                            this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Program.<GetThreadID2>d__2>(ref awaiter, ref <GetThreadID2>d__);
                            return;
                        }
                    }
                    else
                    {
                        awaiter = this.<>u__1;
                        this.<>u__1 = default(TaskAwaiter);
                        this.<>1__state = -1;
                    }
                    awaiter.GetResult();
                    Console.WriteLine("5" + Thread.CurrentThread.ManagedThreadId.ToString());
                }
                catch (Exception exception)
                {
                    this.<>1__state = -2;
                    this.<>t__builder.SetException(exception);
                    return;
                }
                this.<>1__state = -2;
                this.<>t__builder.SetResult();
            }
            [DebuggerHidden]
            void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine){}

            public int <>1__state;

            public AsyncTaskMethodBuilder <>t__builder;

            private TaskAwaiter <>u__1;
        }
    }
}

上面的代碼是反編譯出來的所有代碼,如果各位選擇太長不看的話,這裡還有個簡化版本:

private static Task Main(string[] args) 
<Main>d__.<>t__builder.Start<Program.<Main>d__0>(ref <Main>d__); 
<Main>d__0.MoveNext() 
    <GetThreadID2>d__.<>t__builder.Start<Program.<GetThreadID2>d__2>(ref <GetThreadID2>d__);
    <GetThreadID2>d__2.MoveNext()
    <GetThreadID2>d__2.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Program.<GetThreadID2>d__2>(ref awaiter, ref <GetThreadID2>d__);
    <GetThreadID2>d__2.MoveNext()
<Main>d__0.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Program.<Main>d__0>(ref awaiter, ref <Main>d__); 
<Main>d__0.MoveNext() 

入口

我們先來看一下 Main 方法。

private static Task Main(string[] args)        
{
    Program.<Main>d__0 <Main>d__ = new Program.<Main>d__0();
    <Main>d__.<>t__builder = AsyncTaskMethodBuilder.Create();
    <Main>d__.args = args;
    <Main>d__.<>1__state = -1;
    <Main>d__.<>t__builder.Start<Program.<Main>d__0>(ref <Main>d__);
    return <Main>d__.<>t__builder.Task;
}

首先第一行,構建了一個 Program.<Main>d__0 類型實例,嗯? 這個類哪裡來的,沒寫過呀。
哎嘿,這個類就是編譯器幫我們實現的。每一個 async/await 方法,編譯器都會幫我們實現一個這樣的類。
簡單分析一下這個方法,實例化一個 Program.<Main>d__0(), 初始化 1__state 值為-1,調用 Program.<Main>d__0()Start 方法。

Start

public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
    ExecutionContextSwitcher executionContextSwitcher = default(ExecutionContextSwitcher);
    RuntimeHelpers.PrepareConstrainedRegions();
    try
    {
        ExecutionContext.EstablishCopyOnWriteScope(ref executionContextSwitcher);
        stateMachine.MoveNext();
    }
    finally
    {
        executionContextSwitcher.Undo();
    }
}

Start 代碼的主要作用就是啟動 stateMachine,即 stateMachine.MoveNext()

MoveNext

MoveNext 是 async/await 里非常重要的一段代碼,這個方法控制了整個非同步任務的執行步驟。
我們可以將其分解成三個部分,即

  1. await 之前
  2. await 執行
  3. await 之後的代碼
    具體的分析各位請看註釋。
void IAsyncStateMachine.MoveNext()
{
    // 這裡 <>1__state 之前初始化的時候是 -1,所以 num 就是-1 
    int num = this.<>1__state;
    try
    {
        TaskAwaiter awaiter;
        // 第一次進來,num 是 -1 所以接下來的邏輯
        if (num != 0)
        {
            // 這裡執行 await 之前的代碼
            Console.WriteLine("1" + Thread.CurrentThread.ManagedThreadId.ToString());
            // 這裡就是執行  await 方法
            awaiter = Program.GetThreadID2().GetAwaiter();
            // 判斷是否執行完成,通常第一次進來 IsCompleted 都是 false
            if (!awaiter.IsCompleted)
            {
                // 修改狀態,下次再執行 MoveNext 就不會繼續走這段邏輯來了
                this.<>1__state = 0;
                this.<>u__1 = awaiter;
                Program.<Main>d__0 <Main>d__ = this;
                // 這裡其實是往 Task 里添加了一個 任務結束時的回調,在任務結束時會再次調用 MoveNext  
                // 這就解釋了為什麼 await 之後的方法是另外一個線程,因為await 之後的方法是在 下一個 MoveNext 的里調用的
                // 而那個 MoveNext 是由線程池挑選一個線程進行執行的              
                this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Program.<Main>d__0>(ref awaiter, ref <Main>d__);
                return;
            }
        }
        else
        {
        awaiter = this.<>u__1;
            this.<>u__1 = default(TaskAwaiter);
            this.<>1__state = -1;
        }
        // GetResult 里有一個 死迴圈 等待結果
        awaiter.GetResult();
        // await 之後的方法 
        Console.WriteLine("2" + Thread.CurrentThread.ManagedThreadId.ToString());
        Console.ReadLine();
    }
    catch (Exception exception)
    {
        this.<>1__state = -2;
        this.<>t__builder.SetException(exception);
        return;
    }
    this.<>1__state = -2;
    this.<>t__builder.SetResult();
}

await 之前的代碼

// 這裡執行 await 之前的代碼
Console.WriteLine("1" + Thread.CurrentThread.ManagedThreadId.ToString());

await 之前的代碼很簡單,就是正常依次執行。

await 的代碼

await 的代碼大都以 Task 形式執行,主要分為兩步。
第一步,將 Task 推入線程池中。

awaiter = Task.Run(new Action(Program.<>c.<>9.<GetThreadID2>b__3_0)).GetAwaiter();
public static Task Run(Action action)        
{            
    StackCrawlMark stackCrawlMark = StackCrawlMark.LookForMyCaller;            
    return Task.InternalStartNew(null, action, null, default(CancellationToken), TaskScheduler.Default, TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, ref stackCrawlMark);       
}
internal static Task InternalStartNew(Task creatingTask, Delegate action, object state, CancellationToken cancellationToken, TaskScheduler scheduler, TaskCreationOptions options, InternalTaskOptions internalOptions, ref StackCrawlMark stackMark)        
{                 
    Task task = new Task(action, state, creatingTask, cancellationToken, options, internalOptions | InternalTaskOptions.QueuedByRuntime, scheduler);                      
    task.ScheduleAndStart(false);            
    return task;        
}
internal void ScheduleAndStart(bool needsProtection)
{
    try
    {
     this.m_taskScheduler.InternalQueueTask(this);
    }
    catch (ThreadAbortException exceptionObject){}
}
internal void InternalQueueTask(Task task)        
{                    
    this.QueueTask(task);       
}

第二步,線程池執行 Task。

void IThreadPoolWorkItem.ExecuteWorkItem()        
{            
    this.ExecuteEntry(false);        
}
internal bool ExecuteEntry(bool bPreventDoubleExecution)
{

    if (!this.IsCancellationRequested && !this.IsCanceled)
    {
        this.ExecuteWithThreadLocal(ref Task.t_currentTask);
    }
    return true;
}
private void ExecuteWithThreadLocal(ref Task currentTaskSlot)
{
    Task task = currentTaskSlot;
    TplEtwProvider log = TplEtwProvider.Log;
    Guid currentThreadActivityId = default(Guid);
    bool flag = log.IsEnabled();
    try
    {
        currentTaskSlot = this;
        ExecutionContext capturedContext = this.CapturedContext;
        if (capturedContext == null)
        {
            this.Execute();
        }
        else
        {
            if (this.IsSelfReplicatingRoot || this.IsChildReplica)
            {
                this.CapturedContext = Task.CopyExecutionContext(capturedContext);
            }
            ContextCallback contextCallback = Task.s_ecCallback;
            if (contextCallback == null)
            {
                contextCallback = (Task.s_ecCallback = new ContextCallback(Task.ExecutionContextCallback));
            }
            // 這裡是執行 Task 方法的地方
            ExecutionContext.Run(capturedContext, contextCallback, this, true);
        }
        this.Finish(true);
    }
    finally
    {
        currentTaskSlot = task;
    }
}

await 之後的代碼

那麼 await 之後的代碼是如何執行的呢?
大家回頭看一下 MoveNext 方法,可以看到首次執行 MoveNext 的時候,代碼的邏輯是這樣的

if(num!=0) -> if(!awaiter.IsCompleted) -> AwaitUnsafeOnCompleted;return;

關鍵就在於 AwaitUnsafeOnCompleted 這個方法,來看一下 AwaitUnsafeOnCompleted 方法。

public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine
{
    try
    {
        AsyncMethodBuilderCore.MoveNextRunner runner = null;
        // 這裡是準備一個 任務執行完的回調,簡單理解成是將 MoveNext 包裝成一個 action
        Action completionAction = this.m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? this.Task : null, ref runner);
        if (this.m_coreState.m_stateMachine == null)
        {
            Task<TResult> task = this.Task;
            this.m_coreState.PostBoxInitialization(stateMachine, runner, task);
        }
                // 將 completionAction 推到 task里,具體看一下接下來的代碼
        awaiter.UnsafeOnCompleted(completionAction);
    }
    catch (Exception exception)
    {
        AsyncMethodBuilderCore.ThrowAsync(exception, null);
    }
}
public void UnsafeOnCompleted(Action continuation)        
{            
    TaskAwaiter.OnCompletedInternal(this.m_task, continuation, true, false);        
}
internal static void OnCompletedInternal(Task task, Action continuation, bool continueOnCapturedContext, bool flowExecutionContext)
{
    task.SetContinuationForAwait(continuation, continueOnCapturedContext, flowExecutionContext, ref stackCrawlMark);
}
internal void SetContinuationForAwait(Action continuationAction, bool continueOnCapturedContext, bool flowExecutionContext, ref StackCrawlMark stackMark)
{
     if (!this.AddTaskContinuation(continuationAction, false))
     {
            AwaitTaskContinuation.UnsafeScheduleAction(continuationAction, this);
     }
}
private bool AddTaskContinuation(object tc, bool addBeforeOthers)
{
    //這裡就把completionAction 放到了 Task 的 m_continuationObject 里
    return !this.IsCompleted && ((this.m_continuationObject == null && Interlocked.CompareExchange(ref this.m_continuationObject, tc, null) == null) || this.AddTaskContinuationComplex(tc, addBeforeOthers));
}

至此為止,準備工作(將 MoveNext 放到 task 的回調里)已經做完了,接下來就是如何來觸發這個方法。
簡單分析一下,MoveNext 肯定是在 task 執行完觸發,讓我們回頭看看 Task 執行的代碼。會發現,哎嘿,在ExecutionContext.Run(capturedContext, contextCallback, this, true); 方法執行完後還有一句代碼 this.Finish(true)

internal void Finish(bool bUserDelegateExecuted)
{
    this.FinishStageTwo();
}
internal void FinishStageTwo()
{
    this.FinishStageThree();
}
internal void FinishStageThree()
{
    this.FinishContinuations();
}
internal void FinishContinuations()
{
    // 獲取之前註冊的所有回調  最簡單的就可以理解成 MoveNext
    object obj = Interlocked.Exchange(ref this.m_continuationObject, Task.s_taskCompletionSentinel);
    if (obj != null)
    {
        bool flag = (this.m_stateFlags & 134217728) == 0 && Thread.CurrentThread.ThreadState != ThreadState.AbortRequested && (this.m_stateFlags & 64) == 0;
        Action action = obj as Action;
        if (action != null)
        {
            AwaitTaskContinuation.RunOrScheduleAction(action, flag, ref Task.t_currentTask);
            return;
        }
    }
}
internal static void RunOrScheduleAction(Action action, bool allowInlining, ref Task currentTask)
{
    // 這裡調用了  MoveNextRunner.Run ,簡單理解 就是  MoveNext 方法
    action();
}
void IAsyncStateMachine.MoveNext()
{    
    // 此時 num 就是 0 了,就會執行 await 之後的代碼了。
    int num = this.<>1__state;
    try
    {
        TaskAwaiter awaiter;
        if (num != 0)
        {}
        else
        {
        awaiter = this.<>u__1;
            this.<>u__1 = default(TaskAwaiter);
            this.<>1__state = -1;
        }
        awaiter.GetResult();
        Console.WriteLine("2" + Thread.CurrentThread.ManagedThreadId.ToString());
        Console.ReadLine();
    }
    catch (Exception exception)
    {
    }
    this.<>1__state = -2;
    this.<>t__builder.SetResult();
}

這一段代碼我刪掉了很多內容,但大致的執行順序就是如此,如果想要瞭解更多細節,可以查看 Task 這個類的源碼。

總結

回頭看看開頭的兩個問題,現在就可以回答了。

  1. await 之後的方法是何時執行,如何執行的?
    await 的方法在 Task 執行完成之後,通過調用 Finish 方法執行的。
    具體的執行步驟是先將 MoveNext 方法註冊到 Task 的回調里,然後在 Task 執行完後調用這個方法。
  2. 為什麼 await 之後的代碼會在不同的線程執行?
    這個其實是因為 Task 的機制,Task 會被推到線程池裡,由線程池挑選一個線程去執行,await 之後的代碼其實是由這個線程去執行的,自然就跟 await 的之前的代碼不是一個線程。

PS: 補一張圖


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

-Advertisement-
Play Games
更多相關文章
  • ## 1、說明 一般情況下,都是在model中指定一個資料庫連接參數即可。但某些情況下,相同的庫表會在不同地區都有部署,這個時候需要按地區進行切換(只有一個model情況下)。 ## 2、多model繼承方式 Model層代碼 ``` //A地區的資料庫 class A extends Model ...
  • [TOC] ## 1. 好險,差點被噴 早幾天發了一篇文章:[《僅三天,我用 GPT-4 生成了性能全網第一的 Golang Worker Pool,輕鬆打敗 GitHub 萬星項目》](https://www.danielhu.cn/golang-gopool-1/),這標題是挺容易被懟,哇咔咔; ...
  • 變數 變數是一種可以賦給值的標簽。每一個變數都指向一個相關聯的值,下列代碼中 message 即為變數,指向的值為“Hello Python world !” message = "Hello Python world!" print(message) 第二行的 print() 函數用於列印輸出這個 ...
  • C++ STL 中的非變易演算法(Non-modifying Algorithms)是指那些不會修改容器內容的演算法,是C++提供的一組模板函數,該系列函數不會修改原序列中的數據,而是對數據進行處理、查找、計算等操作,並通過迭代器實現了對序列元素的遍歷與訪問。由於迭代器與演算法是解耦的,因此非變易演算法可以... ...
  • # 將Markdown文件上傳到博客園 # 1.下載python 下載地址為:http://npm.taobao.org/mirrors/python/ 安裝為3.10.11版本 在cmd視窗輸入python,彈出以下視窗為安裝成功 ![image-20230816102551883](https: ...
  • 所謂**數據轉置**,就是是將原始數據表格沿著對角線翻折,使原來的行變成新的列,原來的列變成新的行,從而更方便地進行數據分析和處理。 `pandas`中`DataFrame`的轉置非常簡單,每個`DataFrame`對象都有一個`T`屬性,通過這個屬性就能得到轉置之後的`DataFrame`。下麵介 ...
  • Elasticsearch是一個分散式、RESTful風格的搜索和數據分析引擎,適用於各種數據類型,數字、文本、地理位置、結構化數據、非結構化數據; ...
  • ### 歡迎訪問我的GitHub > 這裡分類和彙總了欣宸的全部原創(含配套源碼):[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) ### 本篇概覽 - 本篇內容並非資料庫相關的核心知識,而是對 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...