CLR via C# 讀書筆記-27.計算限制的非同步操作(上篇)

来源:https://www.cnblogs.com/jdzhang/archive/2018/01/01/8151649.html
-Advertisement-
Play Games

前言 學習這件事情是一個習慣,不能停。。。另外這篇已經看過兩個月過去,但覺得有些事情不總結跟沒做沒啥區別,遂記下此文 1.CLR線程池基礎 2.ThreadPool的簡單使用練習 3.執行上下文 4.協作式取消和超時,System.Threading.CancellationTokenSource的 ...


前言

學習這件事情是一個習慣,不能停。。。另外這篇已經看過兩個月過去,但覺得有些事情不總結跟沒做沒啥區別,遂記下此文

1.CLR線程池基礎

2.ThreadPool的簡單使用練習

3.執行上下文

4.協作式取消和超時,System.Threading.CancellationTokenSource的簡單使用

5.任務

6.任務調度器

一、CLR線程池基礎

如26章所述,創建和銷毀線程是一個昂貴的操作,要耗費大量的時間。另外太多的線程會浪費記憶體資源。由於操作系統必須調度可運行的線程並執行上下文切換,所以太多的線程還對性能不利。

為了改善這個情況,CLR包含了代碼管理它自己的線程池(thread pool),線程池是你的應用程式能使用的線程的集合。

每CLR一個線程池,這個線程池由CLR控制的所有AppDomain共用。

CLR初始化時,線程池中是沒有線程的。在內部,線程池維護了一個操作請求隊列。應用程式執行一個非同步操作時,就調用某個方法,將一個記錄項(entry)追加到線程池的隊列中,線程池的代碼從這個隊列中提取記錄項,將這個記錄項派發(dispatch)給一個線程池線程。如果線程池中沒有線程,就創建一個新線程。

如果應用程式向線程池發出許多請求,線程池會嘗試只用一個線程來服務所有請求。然而,如果你的應用程式發出請求的速度超過了線程池線程處理它們的速度,就會創建額外的線程。

當一個線程池線程閑著沒事一段時間之後,線程會自己醒來終止自己以釋放資源。

二、ThreadPool的簡單使用練習

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"Main Thread,當前線程:{Thread.CurrentThread.ManagedThreadId}");
            ThreadPool.QueueUserWorkItem(Calculate,5);
            Console.WriteLine($"Main Thread doing other work,當前線程:{Thread.CurrentThread.ManagedThreadId}");
            Thread.Sleep(1000);
            Console.WriteLine("hi <Enter> to end this program~~");
            Console.Read();
        }

        //這個方法的簽名必須匹配waitcallback委托
        public static void Calculate(object state)
        {
            //這個方法由一個線程池線程執行
            Console.WriteLine($"In Calculate:state={state},當前線程:{Thread.CurrentThread.ManagedThreadId}");
            Thread.Sleep(1000);
            //這個方法返回後,線程回到池中,等待另一個任務
        }
    }
View Code

運行結果:

有時上圖標註這兩行輸出結果順序會顛倒,這是因為兩個方法相互之間是非同步運行的,windows調度器決定先調度哪一個線程。

三、執行上下文

每個線程都關聯一個執行上下文數據結構。

執行上下文(execution context)包括的東西有安全設置(壓縮棧、Thread的Principal屬性和Windows的身份)、宿主設置(System.Threading.HostExecutionContextManager)以及邏輯調用上下文數據(參見System.Runtime.Remoting.Messaging.CallContext的LogicalSetData和LogicalGetData方法)。

預設情況下,CLR自動造成初始線程的執行上下文“流向”任何輔助線程。這造成將上下文信息傳給輔助線程,但這會對性能造成一定影響。

這是因為執行上下文中包含大量信息,而收集所有這些信息,再把它們複製到輔助線程,要耗費不少時間。

System.Threading.ExecutionContext類,允許你控制線程的執行上下文如何從一個線程“流向”另一個。可用這個類 阻止上下文流動以提升應用程式的性能。

    class Program
    {
        static void Main(string[] args)
        {
            //將一些數據放到Main線程的邏輯調用上下文中
            CallContext.LogicalSetData("Name", "Michael");
            //初始化要由線程池線程做的一些工作
            //線程池線程能訪問邏輯調用上下文結構
            ThreadPool.QueueUserWorkItem(
                state => Console.WriteLine($"Name={CallContext.LogicalGetData("Name")}"));
            //阻止Main線程的執行上下文的流動
            ExecutionContext.SuppressFlow();
            //初始化要由線程池做的工作
            //線程池線程不能訪問邏輯調用上下文數據
            ThreadPool.QueueUserWorkItem(
                state => Console.WriteLine($"Name={CallContext.LogicalGetData("Name")}"));
            //恢復Main線程的執行上下文的流動,
            //以免將來使用更多的線程池線程
            ExecutionContext.RestoreFlow();

            Console.ReadLine();
        }
    }
View Code

編譯後運行結果如下:

四、協作式取消和超時,System.Threading.CancellationTokenSource的簡單使用

Microsoft.NET Framework提供了標準的取消操作模式。這個模式是協作式的,意味著要取消的操作必須顯式支持取消。

CancellationToken實例是輕量級值類型,包含單個私有欄位,即對其CancellationTokenSource對象的引用。

在計算限制操作的迴圈中,可定時調用CancellationToken的IsCancellationRequsted屬性,瞭解迴圈是否應該提前終止,從而終止計算限制的操作。

提前終止的好處在於,CPU不需要再把時間浪費在你對結果不感興趣的操作上。

    static void Main(string[] args)
        {
            Go();
        }

        public static void Go()
        {
            CancellationTokenSource token = new CancellationTokenSource();
            //將CancellationTokenSource和參數 傳入操作
            ThreadPool.QueueUserWorkItem(
                o => Count(token,1000));
            Console.WriteLine($"Hit <Enter> to cancel operation");
            Console.ReadLine();
            token.Cancel();//如果Count方法已返回,Cancel沒有任何效果
            //執行cancel後 立即返回,方法從這裡繼續運行
            Console.ReadLine();
        }
        public static void Count(CancellationTokenSource token,Int32 counto)
        {
            for (int count = 0; count < counto; count++)
            {
                if(token.IsCancellationRequested)
                {
                    Console.WriteLine("操作被取消");
                    break;
                }
                Console.WriteLine(count);
                Thread.Sleep(200); //出於顯示目的而浪費一些時間你
            }
            Console.WriteLine("Count is done");
        }
View Code

運行結果如下圖所示:

可調用CancellationTokenSource的Register方法登記一個或多個在取消一個CancellationTokenSource時調用的方法。

向被取消的CancellationTokenSource登記一個回調方法,將由調用Register的線程調用回調方法(如果為useSynchronizationContext參數傳遞了true值,就可能要通過調用線程的SynchronizationContext進行)。

多次調用Register,多個調用方法都會調用。這些回調方法可能拋出未處理的異常。

如果調用CancellationTokenSource的Cancel方法,向它傳遞true,那麼拋出了未處理異常的第一個回調方法會阻止其他回調方法的執行,拋出的異常也會從Cancel中拋出。

如果調用Cancel並向它傳遞false,那麼登記的所有回調方法都會調用。所有未處理的異常都會添加到一個集合中。所有回調方法都執行好後,其中任何一個拋出了未處理的異常,Cancel就會拋出一個AggregateException,該異常實例的InnerExceptions屬性被設為已拋出的所有異常對象的集合。

        static void Main(string[] args)
        {
            var cts1 = new CancellationTokenSource();
            cts1.Token.Register(() => Console.WriteLine($"cts1被取消"));
            var cts2 = new CancellationTokenSource();
            cts2.Token.Register(() => Console.WriteLine($"cts2被取消"));
            var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token);
            linkedCts.Token.Register(() => Console.WriteLine($"linkedCts 被取消"));
            cts2.Cancel();
            Console.WriteLine($"cts1 canceled={cts1.IsCancellationRequested},cts2 canceled={cts2.IsCancellationRequested}," +
                $"linkedCts={linkedCts.IsCancellationRequested}");
            Console.ReadLine();
    }    
View Code

運行結果如下圖:

如果要在過一段時間後取消操作,要麼用接收延時參數的構造器構造一個CancellationTokenSource對象,要麼調用CancellationTokenSource的CancelAfter方法。

五、任務 

通過觀察,我們發現 ThreadPool最大的問題是沒有內建的機制讓你知道 操作在什麼時候完成,以及操作完成時獲得返回值。鑒於此,Microsoft引入了任務的概念。

下麵展示一個使用task的簡單例子:

        static void Main(string[] args)
        {
            Console.WriteLine($"當前線程ID:{Thread.CurrentThread.ManagedThreadId}");
            //創建一個Task,現在還沒有開始運行
            Task<Int32> t = new Task<int>(n => Sum((Int32)n), 10000);
            //可以後等待任務
            t.Start();
            //可選擇顯示等待任務完成
            t.Wait();
            //可獲得結果(result屬性內部會調用Wait)
            Console.WriteLine($"the Sum is:{t.Result},當前線程ID:{Thread.CurrentThread.ManagedThreadId}");
            Console.ReadLine();
        }    
         private static Int32 Sum(Int32 n)
        {
            Int32 sum = 0;
            for (; n>0; n--)checked
            {
                sum += n; //如果n太大,會拋出System.OverflowException
            }
            Console.WriteLine($"In Sum,當前線程ID:{Thread.CurrentThread.ManagedThreadId}");
            return sum;
        }
View Code

運行結果如右圖:

如果計算限制任務拋出未處理的異常,異常會被“吞噬”並存儲到一個集合中,調用wait方法或Result屬性時,這些成員會拋出一個System.AggregateException對象。

AggregateException提供了一個Handle方法,它為AggregateException中包含的每個異常都調用一個回調方法。回調方法可以為每個異常決定如何對其處理;回調返回true表示異常已處理;返回false表示未處理。調用Handle後,如果至少有一個異常沒有處理,就創建一個新的AggregateException對象,其中只包含未處理的異常。

Task的靜態WaitAny方法會阻塞調用線程,直到數組中的任何Task對象完成。方法返回Int32數組索引值,指明完成的是哪個Task的對象

Task的靜態WaitAll方法也會阻塞調用線程,直到數組中的所有Task對象完成。

下麵演示下task取消操作和task的異常處理

         static void Main(string[] args)
        {
            CancellationTokenSource cts = new CancellationTokenSource();
            Task<Int32> t = Task.Run(() => Sum(cts.Token, 10000), cts.Token);

            cts.Cancel(); 
            try
            {
                Console.WriteLine($"the Sum is:{t.Result},當前線程ID:{Thread.CurrentThread.ManagedThreadId}");
            }
            catch (AggregateException ex)
            {
                //將任何OperationCanceledException對象都是為已處理
                //其他任何異常都造成拋出一個新的AggregateException
                //其中只包含未處理異常
                ex.Handle(e => e is OperationCanceledException);
                Console.WriteLine("Sum was canceled");
            }
            Console.ReadLine();
        }        
         private static Int32 Sum(CancellationToken ct, Int32 n)
        {
            Int32 sum = 0;
            for (; n>0; n--)checked
            {
                //再取消標誌引用的CancellationTokenSource上調用Cancel,
                //下麵這行代碼就會拋出OperationCanceledException
                ct.ThrowIfCancellationRequested();
                sum += n; //如果n太大,會拋出System.OverflowException
            }
            Console.WriteLine($"In Sum,當前線程ID:{Thread.CurrentThread.ManagedThreadId}");
            return sum;
        }
View Code

調用Wait,或者在任務尚未完成時查詢任務的Result屬性,極有可能造成線程池創建新線程,這增大了資源的消耗,也不利於性能和伸縮性。

要知道一個任務在什麼時候結束,任務完成時可啟動另一個任務。

Microsoft為我們提供了ContinueWith,下麵簡單展示使用

        static void Main(string[] args)
        {
            CancellationTokenSource cts = new CancellationTokenSource();
            Task<Int32> t = Task.Run(() => Sum(cts.Token, 10000), cts.Token);
            Task cwt= t.ContinueWith(task => Console.WriteLine($"Sum result is {task.Result}"));
        }        
         private static Int32 Sum(CancellationToken ct, Int32 n)
        {
            Int32 sum = 0;
            for (; n>0; n--)checked
            {
                //再取消標誌引用的CancellationTokenSource上調用Cancel,
                //下麵這行代碼就會拋出OperationCanceledException
                ct.ThrowIfCancellationRequested();
                sum += n; //如果n太大,會拋出System.OverflowException
            }
            Console.WriteLine($"In Sum,當前線程ID:{Thread.CurrentThread.ManagedThreadId}");
            return sum;
        }
View Code

Task對象內部包含了ContinueWith任務的一個集合。可在調用ContinueWith時傳遞對一組TaskContinuationOptions枚舉值進行判斷滿足什麼情況才執行ContinueWith。

偷個懶,哈哈。。。

任務可以啟動多個子任務,下麵簡單展示下使用

        static void Main(string[] args)
        {
            Task<Int32[]> task = new Task<Int32[]>(() =>
            {
                var results = new Int32[3];
                new Task(() => results[0] = Sum(1000), TaskCreationOptions.AttachedToParent).Start();
                new Task(() => results[1] = Sum(1000), TaskCreationOptions.AttachedToParent).Start();
                new Task(() => results[2] = Sum(1000), TaskCreationOptions.AttachedToParent).Start();
                return results;
            });
            var cwt = task.ContinueWith(
                parentTask => Array.ForEach(parentTask.Result, Console.WriteLine));
            task.Start();
            Console.ReadLine();    
        }
        private static Int32 Sum( Int32 n)
        {
            Int32 sum = 0;
            for (; n>0; n--)checked
            {
                sum += n; //如果n太大,會拋出System.OverflowException
            }
            Console.WriteLine($"In Sum,當前線程ID:{Thread.CurrentThread.ManagedThreadId}");
            return sum;
        }    
View Code

TaskCreationOptions.AttachedToParrent標誌將一個Task和創建它的Task關聯,結果是除非所有子任務(以及子任務的子任務)結束運行,否則創建任務(父任務)不認為已經結束。

在一個Task對象的存在期間,可查詢Task的只讀Status屬性瞭解它在其生存期的什麼位置。

 

要創建一組共用相同配置的Task對象。可創建一個任務工廠來封裝通用的配置。即TaskFactory。

在調用TaskFactory或TaskFactory<TResult>的靜態ContinueWhenAll和ContinueWhenAny方法,無論前置任務是如何完成的,ContinueWhenAll和ContinueWhenAny都會執行延續任務。

六、任務調度器

對於不瞭解任務調度的小白來講,可能遇到過下麵這個場景

啊,怎麼會這樣呢?為什麼不能線上程里更新UI組件。

TaskScheduler對象負責執行被調度的任務,同時向Visual Studio調試器公開任務信息。

FCL提供了兩個派生自TaskScheduler的類型:線程池任務調度器(thread pool task scheduler),和同步上下文任務調度器(synchronization context task scheduler)。

預設情況下,所有應用程式使用的都是線程池任務調度器。可查詢TaskScheduler的靜態Default屬性來獲得對預設任務調度器的引用。

同步上下文任務調度器適合提供了圖形用戶界面的應用程式。它將所有任務都調度給應用程式的GUI線程,使所有任務代碼都能成功的更新UI組件。該調度不使用線程池。可執行TaskScheduler的靜態FromCurrentSynchronizationContext方法來獲得對同步上下文任務調度器的引用。

下麵展示一個簡單的例子,演示如何使用同步上下文任務調度器

     public partial class MainForm : Form
    {
        private readonly TaskScheduler m_syncContextTaskScheduler;
        public MainForm()
        {
            //獲得一個對同步上下文任務調度器的引用
            m_syncContextTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
            Text = "Synchronization Context Task Scheduler Demo";
            Visible = true; Width = 400; Height = 400;
        }

        private CancellationTokenSource m_cts;

        protected override void OnMouseClick(MouseEventArgs e)
        {
            if(m_cts!=null)
            {
                m_cts.Cancel(); //一個操作正在運行,取消它
                m_cts = null;
            }
            else
            {
                //任務沒有開始啟動它
                Text = "Operation running"; 
                m_cts = new CancellationTokenSource();
                //這個任務使用預設任務調度器,在一個線程池線程上運行
                Task<Int32> t = Task.Run(()=>Sum(1000),m_cts.Token);
                //這些任務使用 同步上下文任務調度器,在GUI線程上執行
                t.ContinueWith(task => Text = "Result:" + t.Result,
                    CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion,
                    m_syncContextTaskScheduler);
                t.ContinueWith(task => Text = "Operation canceled ",
                    CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled,
                    m_syncContextTaskScheduler);
                t.ContinueWith(task => Text = "Operation defaulted ",
                    CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted,
                    m_syncContextTaskScheduler);
            }


            base.OnMouseClick(e);
        }
        private static Int32 Sum(Int32 n)
        {
            Int32 sum = 0;
            for (; n > 0; n--) checked
                {
                    sum += n; //如果n太大,會拋出System.OverflowException
                }
            return sum;
        }
    }
View Code

單擊窗體的客戶區域,就會線上程池線程上啟動一個計算限制操作。使用線程池線程,因為GUI線程在此期間不會被阻塞,能響應其他UI操作。

 

Parallel就留在下篇來介紹吧。。。

順便講兩句廢話,2017 這一年真快,快的我來不及思考,似乎我就做了一件事,xx項目上線了,與此同時11月從臺北出差回來時,我向領導提出了退出該項目組,項目進展到了一個階段,也算是做人做事有頭有尾吧,並不是說 我放棄了,而是身在其位,就要有擔當,但實在有點累了,覺得不能夠做好後續事情,先暫且為公司做一些擴展工具的事情。這一年也經歷了前前後後,其中原始的成員4個離職了,2個去了廣州出差,踩得坑一個接一個。具體會在另一篇博客中總結一下這一年的項目經驗,開發經驗,以及見識過的高人身上發光點(早該寫了)。。。

深知自己見識、修為淺薄,2018,忌焦躁,忌功利

要做的事情:

1.業餘時間 用net core做一兩個項目

2.參加開源社區項目,回饋社區,不再是一直索取

3.CLR這本書在年前看完並理解

4.接觸一下GO,Docker

5.拓展看一些與專業無關的書,比如 暗時間等(一直想看老是忘記,記下來。。。)

6.鍛煉身體,註意健康,調整作息,老是熬夜不行

好幾次,看到骨仔早上5點都起床了,深感差距好大,好大。啊哈哈。。。


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

-Advertisement-
Play Games
更多相關文章
  • 要讀取鍵盤輸入的數據,需要使用輸入流,可以是位元組輸入流,也可以是位元組輸入流轉換後的字元輸入流。 關於鍵盤輸入,有幾點註意的是:(1).鍵盤輸入流為System.in,其返回的是InputStream類型,即位元組流。(2).位元組流讀取鍵盤的輸入時,需要考慮回車符(\r:13)、換行符(\n:10)。( ...
  • 再有兩天就進入2018了,想想還是要準備一下明年的工作方向。回想當初開始學習函數式編程時的主要目的是想設計一套標準API給那些習慣了OOP方式開發商業應用軟體的程式員們,使他們能用一種接近傳統資料庫軟體編程的方式來實現多線程,並行運算,分散式的數據處理應用程式,前提是這種編程方式不需要對函數式編程語 ...
  • 一、Spring簡介 Spring MVC是當前最優秀的 MVC 框架,自從Spring 2.5 版本發佈後,由於支持註解配置,易用性有了大幅度的提高。Spring 3.0 更加完善,實現了對 Struts 2 的超越。現在越來越多的開發團隊選擇了Spring MVC。 1)Spring3 MVC使 ...
  • 除之前的Spring相關包,還有structs2包外,還需要Hibernate的相關包 首先,Spring整合其他持久化層框架的JAR包 spring-orm-4.2.4.RELEASE.jar (整合Hibernate的) 這個JAR包在Spring框架中包含 Hibernate 需要的JAR包 ...
  • 為什麼需要泛型? 試想你需要一個簡單的容器類,或者說句柄類,比如要存放一個蘋果的籃子,那你可以這樣簡單的實現: 這樣一個簡單的籃子就實現了,但問題是它只能存放蘋果,之後又出現了另外的一大堆水果類,那你就不得不為這些水果類分別實現容器: 然後你發現你其實在做大量的重覆勞動。所以你幻想你的語言編譯器要是 ...
  • 1 兩種文本編輯器:Sublime Text 、 Notepad++ 執行python文件,在命令行中,切換到該Python文件所在的目錄下,然後輸入 Python ###.py命令就可以了。 2 Python的交互模式和直接運行.py文件有什麼區別呢? 直接輸入python進入交互模式,相當於啟動 ...
  • 本文主要介紹Spring中, 1 Spring JDBC 2 使用註解方式管理事務的傳播行為 3 採用XML 方式配置事務 4 SH 整合 5 SSH 整合 一、Spring JDBC 1) 導包 , 使用myeclipse2014, 添加與持久化相關的包 2) 引入名稱空間等 3) 配置數據源 4 ...
  • 描述 本片文章內容屬於ASP.NET MVC系列視圖篇,主要講解View,大致內容如下: 1.Views文件夾講解 2.View種類 3.Razor語法 4.對視圖的基本操作 一 Views文件夾 (一) Views文件夾下常用文件種類 分析: 1.ASP.NET MVC頁面基本被放在Views文件 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...