非同期状態管理

📦 Microsoft.Extensions.AsyncState NuGet パッケージは、現在の非同期コンテキスト内でオブジェクトを格納および取得する機能を提供します。 このパッケージは、特に非同期操作間で複数のオブジェクトを共有する必要がある場合に、 AsyncLocal<T> を直接使用する場合よりもパフォーマンスと使いやすさが向上します。

AsyncState を使用する理由

.NETでは非同期コンテキストでアンビエント データを管理するためのAsyncLocal<T>が提供されますが、それを直接使用すると欠点があります。

  • パフォーマンス: 各 AsyncLocal<T> インスタンスによってオーバーヘッドが増加します。 複数のオブジェクトが非同期コンテキストを通過する必要がある場合、多数の AsyncLocal<T> インスタンスを管理すると、パフォーマンスに影響を与える可能性があります。
  • 抽象化: AsyncLocal<T> を直接使用すると、コードが特定の実装に結合され、将来最適化や変更が困難になります。
  • 有効期間管理: AsyncState パッケージでは、明示的な API を使用してアンビエント データの有効期間をより適切に制御できます。

Microsoft.Extensions.AsyncState パッケージでは、次の情報を提供することで、これらの問題に対処します。

  • 必要な AsyncLocal<T> インスタンスの数を減らす最適化された実装。
  • アンビエント データを格納および取得するためのクリーンな抽象化。
  • 依存関係の挿入との統合により、テストと構成が容易になります。

始める

非同期状態管理を開始するには、Microsoft.Extensions.AsyncState NuGet パッケージをインストールします。

dotnet add package Microsoft.Extensions.AsyncState

詳細については、「dotnet add package または Manage package dependencies in .NET applications」を参照してください。

非同期状態サービスを登録する

AddAsyncState拡張メソッドを使用して、非同期状態サービスを依存関係挿入コンテナーに登録します。

using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();

services.AddAsyncState();

ServiceProvider provider = services.BuildServiceProvider();

この登録により、 IAsyncContext<T> インターフェイスと IAsyncState インターフェイスを依存関係の挿入に使用できるようになります。

IAsyncContext を使用する

IAsyncContext<T> インターフェイスには、現在の非同期コンテキストで値を取得および設定するメソッドが用意されています。

var context = provider.GetRequiredService<IAsyncContext<UserContext>>();

// Set a value in the async context
var userContext = new UserContext { UserId = "12345", UserName = "Alice" };
context.Set(userContext);

// Retrieve the value
if (context.TryGet(out var retrievedContext))
{
    Console.WriteLine($"User: {retrievedContext.UserName}");
}
public record class UserContext
{
    public required string UserId { get; set; }
    public required string UserName { get; set; }
}

コンテキストに設定された値は非同期操作を通過し、同じ非同期コンテキスト内で実行されるすべてのコードで使用できるようにします。

IAsyncState を使用する

IAsyncState インターフェイスは、IAsyncContext<T>拡張される基本ライフサイクル インターフェイスです。 非同期状態のライフサイクルを管理するための Initialize() メソッドと Reset() メソッドを提供します。 非同期状態値に対する型指定されたアクセスを行う場合は、代わりに IAsyncContext<T> を使用します。

実際の例: 要求の関連付け

非同期状態の一般的なユース ケースは、HTTP 要求全体で相関関係情報を維持することです。

public class RequestProcessor(IAsyncContext<CorrelationContext> asyncContext)
{

    public async Task ProcessRequestAsync(string correlationId)
    {
        // Set correlation context at the beginning of request processing
        asyncContext.Set(new CorrelationContext { CorrelationId = correlationId });

        // The correlation ID flows through all async operations
        await Step1Async();
        await Step2Async();
    }

    private async Task Step1Async()
    {
        await Task.Yield();
        
        if (asyncContext.TryGet(out var context))
        {
            Console.WriteLine($"Step 1 - Correlation ID: {context.CorrelationId}");
        }
    }

    private async Task Step2Async()
    {
        await Task.Yield();
        
        if (asyncContext.TryGet(out var context))
        {
            Console.WriteLine($"Step 2 - Correlation ID: {context.CorrelationId}");
        }
    }
}
public class CorrelationContext
{
    public required string CorrelationId { get; set; }
}

この例では、関連付け ID は要求処理の開始時に 1 回設定され、パラメーターとして渡す必要なく、後続のすべての非同期操作で自動的に使用できます。

ASP.NET Core統合

ASP.NET Core アプリケーションでは、非同期状態を使用して、アプリケーションを介して要求固有の情報をフローできます。

public class RequestHandler(IAsyncContext<RequestMetadata> asyncContext)
{

    public async Task<object> GetDataAsync()
    {
        await Task.Yield();
        
        if (asyncContext.TryGet(out var metadata))
        {
            var duration = DateTimeOffset.UtcNow - metadata.StartTime;
            return new
            {
                RequestId = metadata.RequestId,
                RequestPath = metadata.RequestPath,
                Duration = duration.TotalMilliseconds
            };
        }

        return new { Error = "No request metadata available" };
    }
}
public record class RequestMetadata
{
    public required string RequestId { get; set; }
    public required string RequestPath { get; set; }
    public DateTimeOffset StartTime { get; set; }
}

ベスト プラクティス

非同期状態を使用する場合は、次のベスト プラクティスを検討してください。

  • 状態サイズを制限する: 非同期状態オブジェクトを小さくして、メモリのオーバーヘッドを削減し、パフォーマンスを維持します。
  • 状態を早期に初期化する: すべてのダウンストリーム非同期操作で使用できるように、できるだけ早く非同期状態の値を設定します。
  • クリーンアップ状態: 実行時間の長いアプリケーションでメモリ リークを回避するために不要になったときに、非同期状態をリセットまたはクリアします。
  • 適切なインターフェイスを使用する: IAsyncContext<T> を使用して、現在の非同期コンテキスト内で型指定された値を取得および設定します。 初期化とリセットのライフサイクルを直接管理する必要がある場合は、 IAsyncState を使用します。
  • 型の安全性: ジェネリック ディクショナリを使わずに型の安全性を保ち、コードの明確さを向上させるために、特定のコンテキスト型を作成します。

こちらも参照ください