<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      第十一章:C#異步函數與面向對象編程的融合

      第十一章:C#異步函數與面向對象編程的融合


      在現代軟件開發中,異步編程已經成為提升應用程序性能和用戶體驗的關鍵技術。然而,C# 的異步特性是函數式編程范式的產物,與傳統面向對象編程存在本質上的差異。當需要將異步操作融入面向對象的設計中時,你可能會遇到許多獨特的挑戰,例如接口方法的異步化、構造函數中的異步邏輯處理、異步屬性與事件的設計等。

      11.1 異步接口及繼承

      問題

      在接口或基類中定義一個方法時,如何將其異步化?

      解決方案

      核心理念是:async 是一個實現細節,不能直接用于接口或抽象方法的定義,但可以通過返回 TaskTask<T> 來定義異步的簽名。

      • 接口方法或抽象方法:直接返回 TaskTask<T>,表示其實現可能是異步的。
      • 實現方法:在實現時使用 async 關鍵字完成具體的異步操作。

      這樣,異步接口的設計兼容同步實現和異步實現,也符合靈活性和規范性的需求。

      代碼示例

      1. 定義接口

      接口通過返回 TaskTask<T> 的方式定義“異步方法”。這樣的方法本質上是“可等待的”,可以通過 await 使用:

      interface IMyAsyncInterface
      {
          Task<int> CountBytesAsync(HttpClient client, string url);
      }
      
      1. 異步實現
        類實現接口,并在方法體內使用 async/await 執行異步操作:
      class MyAsyncClass : IMyAsyncInterface
      {
          public async Task<int> CountBytesAsync(HttpClient client, string url)
          {
              var bytes = await client.GetByteArrayAsync(url);// 異步操作
              return bytes.Length;
          }
      }
      
      1. 消費異步接口

      在使用該接口時,可以直接通過 await 調用其方法:

      async Task UseMyInterfaceAsync(HttpClient client, IMyAsyncInterface service)
      {
          var result = await service.CountBytesAsync(client, "http://example.com");
          Console.WriteLine(result);
      }
      
      1. 同步實現

      為了方便測試或兼容需求,接口的實現方法可以同步返回結果,而不需要實際執行異步操作:

      class MyAsyncClassStub : IMyAsyncInterface
      {
          public Task<int> CountBytesAsync(HttpClient client, string url)
          {
              // 返回一個固定的結果,而無需異步計算
              return Task.FromResult(42);
          }
      }
      

      注意:如下這種也是普通同步方法,因為方法并沒有異步調用任何方法,只是創建并啟動了一個Task,然后返回這個Task而已,所以是同步方法(編譯器不會為DoSomethingAsync生成狀態機):

      public Task<int> DoSomethingAsync()
      {
         return Task.Run(async () =>
         {
             await Task.Delay(1000);
             return 5;
         });
      }
      

      但是如果是這樣寫,那就是異步方法了(編譯器會為DoSomethingAsync生成狀態機):

      public async Task<int> DoSomethingAsync()
      {
         return await Task.Run(async () =>
         {
             await Task.Delay(1000);
             return 5;
         });
      }
      

      小結

      1. 接口方法不能包含 async 關鍵字

        • async 是方法實現的一部分,用于告訴編譯器生成狀態機來處理異步操作,而接口和抽象類僅定義方法的簽名,不涉及實現細節。因此,接口和抽象類的方法不能使用 async
      2. 異步接口的本質

        • 異步接口的核心在于返回類型是 TaskTask<T>,而不是方法本身是否使用 async
        • 調用方只需知道方法返回可等待的任務,而無需關心具體實現是異步的還是同步的。
      3. 異步任務的本質

        • 一個 TaskTask<T> 代表一項任務,可能是未完成的任務(將在未來完成)或已完成的任務(立即返回結果)。
        • 通過這種機制,調用方可以統一使用 await 等待任務的完成,而不需要感知任務的具體狀態。
      4. 實現的靈活性

        • 接口方法可以通過真正的異步操作(如 asyncawait)實現,也可以通過同步方式(如 Task.FromResult)實現。
        • 這種靈活性允許在不同場景下選擇合適的實現策略:
          • 如果存在實際的異步操作(如 I/O、網絡請求),應采用真正的異步實現。
          • 如果不需要異步操作,可以返回一個已完成的任務(例如 Task.FromResult)。
      5. 異步化策略

        • 優先異步:當方法涉及 I/O 操作(如文件讀寫、網絡請求)時,應優先采用異步實現,以提升性能并避免阻塞線程。
        • 避免不必要的 async 修飾:如果方法沒有實際的異步工作(例如只是返回固定結果),應避免添加不必要的 async 修飾,直接返回任務(如 Task.FromResult),以提高性能。

      11.2 異步構造方法:工廠模式

      問題

      在某些場景下,我們需要在對象的構造過程中執行異步操作(例如加載配置文件、從遠程服務拉取數據等)。然而,C# 的構造函數不支持 asyncawait,因為構造函數本身無法返回 Task

      解決方案:異步工廠模式

      一種優雅的解決方案是使用異步工廠方法模式,讓類自身提供一個靜態的異步工廠方法來創建和初始化實例。這種方法將初始化的異步邏輯與實例創建綁定在一起,確保只有在完成初始化后才可以訪問實例。

      以下是一個完整的異步工廠模式代碼示例:

      using System;
      using System.Threading.Tasks;
      
      class MyAsyncClass
      {
          // 私有構造器,防止直接實例化
          private MyAsyncClass()
          {
              Console.WriteLine("Constructor called");
          }
      
          // 異步初始化方法,私有
          private async Task<MyAsyncClass> InitializeAsync()
          {
              Console.WriteLine("Initializing asynchronously...");
              await Task.Delay(1000); // 模擬異步操作
              Console.WriteLine("Initialization complete");
              return this;
          }
      
          // 靜態工廠方法
          public static Task<MyAsyncClass> CreateAsync()
          {
              var instance = new MyAsyncClass();
              return instance.InitializeAsync();
          }
      }
      
      class Program
      {
          static async Task Main(string[] args)
          {
              Console.WriteLine("Creating instance...");
              MyAsyncClass instance = await MyAsyncClass.CreateAsync();
              Console.WriteLine("Instance created and ready to use!");
          }
      }
      

      執行結果:

      Creating instance...
      Constructor called
      Initializing asynchronously...
      Initialization complete
      Instance created and ready to use!
      

      設計要點

      • 構造函數和 InitializeAsync 方法為私有,防止直接調用。
      • 通過靜態工廠方法 CreateAsync 創建實例,并確保初始化完成后再返回對象。

      常見問題與反例

      反例:在構造器中啟動異步操作

      class MyAsyncClass
      {
          public MyAsyncClass()
          {
              InitializeAsync();
          }
      
          // 錯誤:使用 async void
          private async void InitializeAsync()
          {
              await Task.Delay(TimeSpan.FromSeconds(1)); // 模擬異步操作
          }
      }
      

      問題:

      1. 未完成的實例: 構造函數返回時,實例的異步初始化尚未完成,調用方可能誤用未-準備好的實例。
      2. 異常無法捕獲:由于 async void 的特殊性,InitializeAsync 方法中拋出的異常無法通過構造函數外的 try-catch 捕獲。
      3. 不確定的狀態:調用方無法得知異步操作何時完成。

      異步工廠模式的優點

      1. 安全性

        • 確保實例在初始化完成后才可用,避免未初始化實例被錯誤使用。
      2. 封裝性

        • 初始化邏輯被封裝在工廠方法中,調用方無需關心實現細節。
      3. 可維護性

        • 異步工廠模式強制調用者按正確的方式使用類型,減少潛在錯誤。

      異步工廠模式的局限性

      盡管異步工廠模式是解決異步構造問題的推薦方法,但在某些場景中可能會面臨以下局限性:

      1. 與依賴注入框架的不兼容

        • 大多數依賴注入(DI)框架(例如 ASP.NET Core 的 DI 容器)無法處理異步工廠方法。這是因為 DI 容器通常會直接調用構造器來創建對象,而無法等待異步工廠方法。

        • 解決方案

          • 如果初始化是共享資源,可以使用惰性初始化(如 AsyncLazy)。
          • 如果必須支持異步初始化,可以參考下一節的異步初始化模式
      2. 代碼復雜性

        • 對于簡單的類型,異步工廠模式可能顯得過于復雜。如果初始化邏輯非常簡單,可以考慮讓調用方顯式調用異步初始化方法(盡管可能存在調用方忘記調用的問題)。

      11.3 異步構造:異步初始化模式

      問題

      在某些情況下,無法使用工廠模式(參見 11.2 節),例如實例是通過以下方式創建的:

      • 依賴注入(DI)容器:例如 ASP.NET Core 中的 DI。
      • 反射:如 Activator.CreateInstance。
      • 數據綁定

      此時我們需要一種機制來支持異步初始化,同時避免初始化過程中導致實例狀態不一致。

      解決方案:異步初始化模式

      異步初始化模式的核心思想是:

      1. 在構造器中啟動異步初始化,并將初始化的任務暴露為一個公共屬性 Initialization
      2. 調用方可以通過檢查并等待 Initialization 屬性,確保實例完成初始化后再使用。

      代碼示例

      1. 定義標記接口

      為了規范所有需要異步初始化的類型,可以定義一個接口來強制實現 Initialization 屬性:

      /// <summary>
      /// 標記需要異步初始化的類型,并提供初始化任務
      /// </summary>
      public interface IAsyncInitialization
      {
          /// <summary>
          /// 異步初始化任務
          /// </summary>
          Task Initialization { get; }
      }
      

      2. 實現基本類型

      public class MyFundamentalType : IAsyncInitialization
      {
          public MyFundamentalType()
          {
              // 構造函數中啟動異步初始化
              Initialization = InitializeAsync();
          }
      
          public Task Initialization { get; private set; }
      
          private async Task InitializeAsync()
          {
              // 模擬異步初始化
              await Task.Delay(TimeSpan.FromSeconds(1));
          }
      }
      

      3. 使用場景
      通過依賴注入框架或反射創建實例:

      IMyFundamentalType instance = UltimateDIFactory.Create<IMyFundamentalType>();
      if (instance is IAsyncInitialization instanceAsyncInit)
      {
          // 等待異步初始化完成
          await instanceAsyncInit.Initialization;
      }
      

      4. 合成類型支持
      合成類型可能依賴多個需要異步初始化的組件:

      public class MyComposedType : IAsyncInitialization
      {
          private readonly IMyFundamentalType _fundamental;
      
          public MyComposedType(IMyFundamentalType fundamental)
          {
              _fundamental = fundamental;
              Initialization = InitializeAsync();
          }
      
          public Task Initialization { get; private set; }
      
          private async Task InitializeAsync()
          {
              // 如果組件實現了異步初始化,則等待其完成
              if (_fundamental is IAsyncInitialization fundamentalInit)
              {
                  await fundamentalInit.Initialization;
              }
      
              // 執行自身的初始化邏輯
              await Task.Delay(TimeSpan.FromSeconds(1));
          }
      }
      

      5. 簡化多組件初始化
      可通過輔助方法簡化對多個組件的初始化檢查:

      public static class AsyncInitialization
      {
          public static Task WhenAllInitializedAsync(params object[] instances)
          {
              return Task.WhenAll(instances
                  .OfType<IAsyncInitialization>() // 篩選出需要異步初始化的實例
                  .Select(x => x.Initialization)); // 收集初始化任務
          }
      }
      
      // 示例:合成類型依賴多個注入組件
      private async Task InitializeAsync()
      {
          await AsyncInitialization.WhenAllInitializedAsync(_fundamental, _anotherType, _yetAnother);
          // 自身的初始化邏輯
      }
      

      優缺點分析

      優點

      1. 兼容性強:支持反射創建、依賴注入、數據綁定等場景。
      2. 異步初始化管理清晰:通過 Initialization 屬性集中管理異步狀態。
      3. 靈活性:初始化可異步也可同步完成,允許根據具體情況實現。

      缺點

      1. 暴露未初始化實例:與異步工廠模式不同,該模式允許調用方在初始化未完成時訪問實例,可能導致使用未完全準備好的實例。
      2. 復雜性:當組件依賴關系復雜時,需要額外代碼管理依賴的初始化順序和狀態。

      最佳實踐

      1. 優先使用工廠模式:如果可能,盡量采用 11.2 節中的異步工廠模式,避免直接暴露未初始化的實例。
      2. 謹慎依賴異步初始化:減少對異步初始化的依賴,優先設計為延遲初始化。
      3. 輔助工具類簡化邏輯:對于復雜依賴關系,使用輔助方法(如 AsyncInitialization.WhenAllInitializedAsync)來減少冗余代碼。

      11.4 異步屬性

      問題

      在實際開發中,可能會遇到需要將某個屬性轉換為異步操作的場景。比如,當屬性的 getter 方法需要執行異步操作時,如何正確地處理?然而,C# 中并沒有異步屬性的概念(即 async 屬性),也無法直接在屬性中使用 async 關鍵字。這種限制實際上是有意義的,因為屬性的設計語義是用于快速獲取數據,而不是啟動復雜的后臺操作。

      以下是一個典型的錯誤示例(代碼無法編譯):

      // 錯誤:嘗試將屬性變為異步
      public int Data
      {
          async get
          {
              await Task.Delay(TimeSpan.FromSeconds(1));
              return 13;
          }
      }
      

      因此,當發現需要“異步屬性”時,實際上應該重新審視設計思路,根據場景選擇合適的實現方式。

      解決方案:兩種設計選擇

      在需要異步屬性的場景中,通常存在兩種需求:

      1. 每次訪問屬性時,重新啟動異步計算。
      2. 只進行一次異步計算并緩存結果,之后的訪問返回相同的值。

      根據這兩種需求,可以分別采用以下解決方案:

      方案 1:屬性值需要重復異步計算

      在這種情況下,屬性的行為本質上是一個異步方法,因為每次訪問屬性時都會重新啟動異步操作。這種設計不適合用屬性實現,而應該改為顯式的異步方法。

      示例:

      // 使用異步方法代替屬性
      public async Task<int> GetDataAsync()
      {
          await Task.Delay(TimeSpan.FromSeconds(1)); // 模擬異步操作
          return 13;
      }
      

      調用代碼:

      int value = await instance.GetDataAsync();
      

      注意:雖然也可以通過屬性返回 Task<T>,如下所示:

      // 返回 Task<T> 的屬性
      public Task<int> Data
      {
          get { return GetDataAsync(); }
      }
      

      但是,這種設計會讓 API 產生誤導。調用方在讀取 Data 屬性時,可能會誤以為是普通的同步屬性,而不清楚其行為是異步的。因此,不推薦使用這種方式。

      方案 2:異步計算值并緩存

      如果屬性值只需要異步計算一次,并在后續訪問中返回相同的結果,可以使用異步延遲初始化的方式。這種方式非常適合用屬性來實現,并且符合屬性的語義。

      示例:

      public AsyncLazy<int> Data { get; }
      
      private readonly AsyncLazy<int> _data = new AsyncLazy<int>(async () =>
      {
          await Task.Delay(TimeSpan.FromSeconds(1)); // 模擬異步操作
          return 13;
      });
      

      調用代碼:

      int value = await instance.Data; // 異步獲取值
      

      在這個實現中,AsyncLazy 確保異步計算只會執行一次,之后的訪問直接返回緩存的結果。

      反面示例和注意事項

      在將同步屬性改為異步時,務必避免如下的反面示例:

      private async Task<int> GetDataAsync()
      {
          await Task.Delay(TimeSpan.FromSeconds(1));
          return 13;
      }
      
      // 錯誤:在屬性中調用異步方法并阻塞
      public int Data
      {
          get { return GetDataAsync().Result; } // 阻塞等待異步操作完成
      }
      
      • 問題 1:性能和線程阻塞
        使用 .Result.Wait() 會阻塞當前線程,違背了異步編程的初衷,可能導致死鎖或性能問題。

      • 問題 2:語義不清晰
        屬性的語義應是快速訪問數據,而不是啟動復雜的操作。上述代碼在 API 設計上容易誤導調用方。

      狀態屬性與異步語義

      在將同步代碼轉換為異步代碼時,還需要特別注意屬性的狀態語義問題。以流操作中的 Stream.Position 為例:

      • 在同步方法 Stream.ReadStream.Write 中,Position 會在操作完成后更新,反映當前的流位置。
      • 在異步方法 Stream.ReadAsyncStream.WriteAsync 中,Position 的更新時機可能會產生歧義:
        • 是在異步操作完成后更新?
        • 還是在調用 ReadAsyncWriteAsync 方法時立即更新?

      這些語義問題在異步化過程中需要特別考慮,并且應在 API 文檔中清晰說明。

      完整代碼示例

      以下是一個完整的示例,展示了異步方法和異步延遲初始化的兩種實現:

      using System;
      using System.Threading.Tasks;
      
      class MyClass
      {
          // 異步方法:每次調用都會重新計算值
          public async Task<int> GetDataAsync()
          {
              await Task.Delay(TimeSpan.FromSeconds(1)); // 模擬異步操作
              return 13;
          }
      
          // 異步延遲初始化:值只計算一次并緩存
          public AsyncLazy<int> Data { get; }
      
          private readonly AsyncLazy<int> _data = new AsyncLazy<int>(async () =>
          {
              await Task.Delay(TimeSpan.FromSeconds(1)); // 模擬異步操作
              return 13;
          });
      
          public MyClass()
          {
              Data = _data;
          }
      }
      
      class Program
      {
          static async Task Main(string[] args)
          {
              var myClass = new MyClass();
      
              // 使用異步方法
              int value1 = await myClass.GetDataAsync();
              Console.WriteLine($"Value from GetDataAsync: {value1}");
      
              // 使用異步延遲初始化
              int value2 = await myClass.Data;
              Console.WriteLine($"Value from AsyncLazy<Data>: {value2}");
          }
      }
      

      輸出:

      Value from GetDataAsync: 13
      Value from AsyncLazy<Data>: 13
      

      通過這種方式,屬性與方法的語義更加明確,既滿足了異步計算的需求,又避免了設計上的歧義。

      11.5 異步事件

      問題

      在設計異步事件時,如何跟蹤事件處理程序的完成情況?通常情況下,事件的觸發者不需要關心處理程序是否完成,這種情形多見于通知事件(如按鈕點擊)。但在某些情況下,觸發者需要等待所有處理程序完成(如生命周期事件),這被稱為命令事件。

      一個常見的挑戰是,async void 異步處理程序無法被直接跟蹤,因為它不會返回 Task,因此我們需要一種替代方法來檢測異步處理程序的完成狀態。

      解決方案:使用延遲管理器

      為了解決這個問題,可以引入延遲管理器DeferralManager),它可以跟蹤事件處理程序的延遲狀態:

      1. 處理程序分配延遲:延遲管理器為每個異步處理程序分配一個延遲對象,用于跟蹤異步處理的狀態。
      2. 延遲完成通知:當異步處理完成時,延遲對象會通知延遲管理器。
      3. 等待所有延遲完成:事件發送方可以等待延遲管理器跟蹤的所有延遲完成后再繼續執行。

      實現步驟

      1. 定義事件參數類型

      為了支持延遲管理,事件參數類型需要擴展。可以通過實現 IDeferralSource 接口,并包含一個 DeferralManager 實例來管理延遲。

      public class MyEventArgs : EventArgs, IDeferralSource
      {
          private readonly DeferralManager _deferrals = new DeferralManager();
      
          // 獲取延遲對象
          public IDisposable GetDeferral()
          {
              return _deferrals.DeferralSource.GetDeferral();
          }
      
          // 等待所有異步延遲完成
          internal Task WaitForDeferralsAsync()
          {
              return _deferrals.WaitForDeferralsAsync();
          }
      }
      

      MyEventArgs 中:

      • GetDeferral 方法用于分配延遲對象。
      • WaitForDeferralsAsync 方法用于等待所有延遲完成。

      2. 觸發異步事件

      當觸發事件時,需要等待所有異步處理程序完成。可以使用以下代碼觸發事件:

      public event EventHandler<MyEventArgs> MyEvent;
      
      private async Task RaiseMyEventAsync()
      {
          // 獲取事件處理程序
          EventHandler<MyEventArgs> handler = MyEvent;
          if (handler == null)
              return;
      
          // 創建事件參數
          var args = new MyEventArgs();
      
          // 觸發事件
          handler(this, args);
      
          // 等待所有異步處理程序完成
          await args.WaitForDeferralsAsync();
      }
      

      代碼解讀:

      1. 檢查是否有訂閱的處理程序。
      2. 創建 MyEventArgs 實例。
      3. 調用事件處理程序。
      4. 調用 WaitForDeferralsAsync 等待所有異步處理程序完成。

      3. 異步事件處理程序

      事件處理程序可以通過以下方式分配延遲,并在異步操作完成后通知延遲管理器:

      async void AsyncHandler(object sender, MyEventArgs args)
      {
          using IDisposable deferral = args.GetDeferral(); // 分配延遲
          await Task.Delay(TimeSpan.FromSeconds(2));       // 模擬異步操作
      }
      

      在這里,using 塊確保延遲對象在異步操作完成后被正確釋放,以此通知延遲管理器。

      完整實現示例

      以下是一個完整的代碼示例,展示如何使用延遲管理器實現異步事件:

      using System;
      using System.Threading.Tasks;
      using Nito.AsyncEx; // 引入 Nito.AsyncEx 庫
      
      // 定義事件參數類型,支持延遲管理
      public class MyEventArgs : EventArgs, IDeferralSource
      {
          private readonly DeferralManager _deferrals = new DeferralManager();
      
          // 獲取延遲對象
          public IDisposable GetDeferral()
          {
              return _deferrals.DeferralSource.GetDeferral();
          }
      
          // 等待所有異步延遲完成
          internal Task WaitForDeferralsAsync()
          {
              return _deferrals.WaitForDeferralsAsync();
          }
      }
      
      public class MyEventSource
      {
          // 定義事件
          public event EventHandler<MyEventArgs> MyEvent;
      
          // 觸發事件并等待異步處理程序完成
          public async Task RaiseMyEventAsync()
          {
              EventHandler<MyEventArgs> handler = MyEvent;
              if (handler == null)
                  return;
      
              // 創建事件參數
              var args = new MyEventArgs();
      
              // 觸發事件
              handler(this, args);
      
              // 等待所有異步處理程序完成
              await args.WaitForDeferralsAsync();
          }
      }
      
      public class Program
      {
          public static async Task Main(string[] args)
          {
              var source = new MyEventSource();
      
              // 注冊異步事件處理程序
              source.MyEvent += async (sender, e) =>
              {
                  using IDisposable deferral = e.GetDeferral(); // 分配延遲
                  Console.WriteLine("Handler started.");
                  await Task.Delay(TimeSpan.FromSeconds(2));    // 模擬異步操作
                  Console.WriteLine("Handler completed.");
              };
      
              // 觸發事件并等待處理程序完成
              Console.WriteLine("Raising event...");
              await source.RaiseMyEventAsync();
              Console.WriteLine("Event processing completed.");
          }
      }
      

      輸出

      Raising event...
      Handler started.
      Handler completed.
      Event processing completed.
      

      通知事件 vs 命令事件

      .NET 中的事件可以按語義分為兩種:

      1. 通知事件

        • 用于通知訂閱方某些情況發生。
        • 通常是單向的,事件發送方并不關心訂閱方是否完成處理。
        • 特點:無需額外代碼支持異步處理程序。例如,按鈕單擊事件屬于通知事件。
        • 處理方式:異步處理程序可以是 async void,發送方不需要等待其完成。
      2. 命令事件

        • 用于觸發某些功能,發送方需要等待訂閱方完成處理后才能繼續。
        • 特點:發送方需要檢測訂閱方的完成狀態,異步處理程序需要顯式等待。
        • 處理方式:需要引入延遲機制(如 DeferralManager),以跟蹤異步處理狀態。

      最佳實踐

      1. 延遲的作用范圍
        延遲機制主要針對命令事件,因為命令事件需要等待處理程序完成。而對于通知事件,這種機制是不必要的。

      2. 線程安全性
        事件參數的類型應該是線程安全的。最簡單的實現方式是使事件參數不可變(所有屬性均為只讀)。

      3. Nito.AsyncEx
        使用 DeferralManager 是一種簡潔的擴展方式,可從 NuGet 包 Nito.AsyncEx 中直接獲取。

      4. 異步事件的設計原則

        • 如果事件是通知性質的,無需額外代碼支持異步處理程序。
        • 如果事件是命令性質的,需要明確等待處理程序完成,且需要文檔清晰說明其語義。

      11.6 異步釋放

      問題

      在某些類型中,需要在釋放(Dispose)資源時處理異步操作。如何實現異步資源釋放,同時確保語義清晰且與現有的 .NET 生態系統兼容?

      解決方案

      .NET 提供了兩種常見模式來實現資源釋放:

      1. 釋放作為取消:將釋放視為對所有正在進行操作的取消請求。
      2. 異步釋放:使用異步語義,在釋放資源時等待異步操作完成。

      1. 將釋放視為取消

      這種模式適用于需要在釋放資源時取消現有操作的場景,例如 HttpClient。通過使用 CancellationTokenSource 來取消當前操作,避免資源占用。

      實現代碼:

      class MyClass : IDisposable
      {
          private readonly CancellationTokenSource _disposeCts = new CancellationTokenSource();
      
          public async Task<int> CalculateValueAsync(CancellationToken cancellationToken = default)
          {
              using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _disposeCts.Token);
              await Task.Delay(TimeSpan.FromSeconds(2), combinedCts.Token);
              return 13;
          }
      
          public void Dispose()
          {
              _disposeCts.Cancel();
          }
      }
      

      用法:

      async Task UseMyClassAsync()
      {
          Task<int> task;
          using (var resource = new MyClass())
          {
              task = resource.CalculateValueAsync();
          }
      
          // 在 Dispose 后調用 await 將拋出 OperationCanceledException
          var result = await task;
      }
      

      2. 異步釋放(IAsyncDisposable)

      異步釋放在 C# 8.0 和 .NET Core 3.0 中引入,通過 IAsyncDisposable 接口和 DisposeAsync 方法實現。在釋放資源時,可以等待異步操作完成。

      實現代碼:

      class MyClass : IAsyncDisposable
      {
          public async ValueTask DisposeAsync()
          {
              // 模擬異步釋放操作
              await Task.Delay(TimeSpan.FromSeconds(2));
          }
      }
      

      用法:
      使用 await using 語法來異步釋放資源:

      await using (var myClass = new MyClass())
      {
          // 使用資源
      }
      // 此處調用并等待 DisposeAsync
      

      如果需要避免捕獲同步上下文,可以使用 ConfigureAwait(false):

      var myClass = new MyClass();
      await using (myClass.ConfigureAwait(false))
      {
          // 使用 myClass 的邏輯
      }
      // 此處調用 DisposeAsync 并避免上下文捕獲
      

      注意DisposeAsync 返回 ValueTask 而非 Task。這可以減少內存分配成本,但兩者都支持標準的 async/await 語法。

      兩種模式的比較

      模式 優勢 劣勢
      釋放作為取消 簡單且廣泛兼容,適用于多數場景。 無法等待未完成的操作。
      異步釋放 支持異步操作完成,適用于需要嚴格釋放的資源。 實現復雜,依賴 C# 8.0 及更高版本。

      在某些場景下,兩種模式可以結合使用:

      • 如果客戶端使用 Dispose,表示取消正在進行的操作。
      • 如果客戶端使用 DisposeAsync,則等待所有操作完成并釋放資源。

      組合實現代碼:

      class MyClass : IDisposable, IAsyncDisposable
      {
          private readonly CancellationTokenSource _disposeCts = new CancellationTokenSource();
      
          public async Task<int> CalculateValueAsync(CancellationToken cancellationToken = default)
          {
              using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _disposeCts.Token);
              await Task.Delay(TimeSpan.FromSeconds(2), combinedCts.Token);
              return 13;
          }
      
          public void Dispose()
          {
              _disposeCts.Cancel(); // 取消操作
          }
      
          public async ValueTask DisposeAsync()
          {
              _disposeCts.Cancel(); // 取消操作
              await Task.Delay(TimeSpan.FromSeconds(2)); // 模擬異步釋放資源
          }
      }
      
      posted @ 2024-12-09 16:14  平元兄  閱讀(160)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 国产精品午夜精品福利| 中文区中文字幕免费看| 久久精品国产亚洲精品色婷婷| 亚洲国产精品一区二区久久| 亚洲熟女乱色综合亚洲图片| 吉木萨尔县| 一本一道av中文字幕无码| 狠狠做五月深爱婷婷天天综合| av中文字幕在线二区| 欧美大胆老熟妇乱子伦视频| 色综合天天综合网天天看片| 国产精品日韩中文字幕熟女| 免费人成再在线观看视频| 狠狠综合久久av一区二| 午夜福利视频| 国产不卡精品视频男人的天堂| 日本一道一区二区视频| 丰满人妻AV无码一区二区三区| 国产精品小仙女自拍视频| 日本成熟少妇喷浆视频| 九月婷婷人人澡人人添人人爽| 国产大学生粉嫩无套流白浆| 日本黄色三级一区二区三区| 奇米四色7777中文字幕| 国产精品免费看久久久| 亚洲色大成网站www在线| 亚洲国产成人精品无码区蜜柚| 亚洲男人天堂2018| 18岁日韩内射颜射午夜久久成人| 粉嫩av一区二区三区蜜臀| 国内不卡不区二区三区| 商丘市| 国产在线中文字幕精品| 久久久一本精品99久久精品88| 欧美不卡无线在线一二三区观| 在线看片免费人成视频久网| 欧美拍拍视频免费大全| 婷婷六月天在线| bt天堂新版中文在线| 欧美熟妇xxxxx欧美老妇不卡| 天天做天天爱夜夜爽女人爽|