欧美成人精品手机在线观看_69视频国产_动漫精品第一页_日韩中文字幕网 - 日本欧美一区二区

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

C#.NET為什么需要[EnumeratorCancellation]?

freeflydom
2024年11月19日 8:44 本文熱度 474

在使用 C# 編寫異步迭代器時,您可能會遇到如下警告:

warning CS8425: 異步迭代器“TestConversationService.ChatStreamed(IReadOnlyList<ChatMessage>, ChatCompletionOptions, CancellationToken)”具有一個或多個類型為 "CancellationToken" 的參數(shù),但它們都未用 "EnumeratorCancellation" 屬性修飾,因此將不使用所生成的 "IAsyncEnumerable<>.GetAsyncEnumerator" 中的取消令牌參數(shù)。

看到這樣的警告,您可能會困惑:究竟需要在異步迭代器的方法參數(shù)上添加 [EnumeratorCancellation] 屬性嗎?如果不添加,會有什么區(qū)別? 讓我們深入探討一下這個問題,揭示其背后的真相。

正常調(diào)用時,[EnumeratorCancellation] 的影響

如果您只是簡單地在異步迭代器方法中傳遞一個普通的 CancellationToken,無論是否使用 [EnumeratorCancellation],方法的行為似乎并沒有顯著區(qū)別。例如:

public async IAsyncEnumerable<int> GenerateNumbersAsync(CancellationToken cancellationToken = default)
{
    for (int i = 0; i < 10; i++)
    {
        cancellationToken.ThrowIfCancellationRequested();
        yield return i;
        await Task.Delay(1000, cancellationToken);
    }
}
public async Task ConsumeNumbersAsync()
{
    CancellationTokenSource cts = new CancellationTokenSource();
    Task cancelTask = Task.Run(async () =>
    {
        await Task.Delay(3000);
        cts.Cancel();
    });
    try
    {
        await foreach (var number in GenerateNumbersAsync(cts.Token))
        {
            Console.WriteLine(number);
        }
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("枚舉已被取消");
    }
    await cancelTask;
}

輸出如下:

0
1
2
枚舉已被取消

在上述代碼中,即使沒有使用 [EnumeratorCancellation],取消令牌 cts.Token 依然會生效,導致迭代過程被取消。這可能會讓開發(fā)者誤以為 [EnumeratorCancellation] 沒有實際作用,進而引發(fā)更多的困惑。

揭開真相:生產(chǎn)者與消費者的職責分離

實際上,[EnumeratorCancellation] 的核心作用在于 實現(xiàn)生產(chǎn)者與消費者的職責分離。具體來說:

  • 生產(chǎn)者(即提供數(shù)據(jù)的異步迭代方法)專注于數(shù)據(jù)的生成和響應(yīng)取消請求,不關(guān)心取消請求的來源或何時取消。

  • 消費者(即使用數(shù)據(jù)的部分)負責控制取消邏輯,獨立地決定何時取消整個迭代過程。

通過這種設(shè)計,生產(chǎn)者不需要知道取消請求是由誰或何時發(fā)起的,簡化了生產(chǎn)者的設(shè)計,同時賦予消費者更大的控制權(quán)。這不僅提高了代碼的可維護性和可復用性,還避免了取消邏輯的混亂。

示例說明

下面通過一個示例,直觀地展示 [EnumeratorCancellation] 如何實現(xiàn)職責分離。

1. 定義異步迭代器方法

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
public class DataProducer
{
    public async IAsyncEnumerable<int> ProduceData(
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        int i = 0;
        while (true)
        {
            cancellationToken.ThrowIfCancellationRequested();
            Console.WriteLine($"[Iterator] 生成數(shù)字: {i}");
            yield return i++;
            await Task.Delay(1000, cancellationToken); // 模擬數(shù)據(jù)生成延遲
        }
	}
}

在這個 DataProducer 類中,ProduceData 方法使用 [EnumeratorCancellation] 標注了 cancellationToken 參數(shù)。這意味著,當消費者通過 WithCancellation 傳遞取消令牌時,編譯器會自動將該取消令牌傳遞給 ProduceData 方法的 cancellationToken 參數(shù)。

2. 定義消費者方法

using System;
using System.Threading;
using System.Threading.Tasks;
public class DataConsumer
{
	public async Task ConsumeDataAsync(IAsyncEnumerable<int> producer)
	{
		using CancellationTokenSource cts = new CancellationTokenSource();
		// 在5秒后發(fā)出取消請求
		_ = Task.Run(async () =>
		{
			await Task.Delay(5000);
			cts.Cancel();
			Console.WriteLine("[Trigger] 已發(fā)出取消請求");
		});
		try
		{
			// 通過 WithCancellation 傳遞取消令牌
			await foreach (var data in producer.WithCancellation(cts.Token))
			{
				Console.WriteLine($"[Consumer] 接收到數(shù)據(jù): {data}");
			}
		}
		catch (OperationCanceledException)
		{
			Console.WriteLine("[Consumer] 數(shù)據(jù)接收已被取消");
		}
	}
}

在 DataConsumer 類中,ConsumeDataAsync 方法創(chuàng)建了一個 CancellationTokenSource,并在5秒后取消它。通過 WithCancellation 方法,將取消令牌傳遞給 ProduceData 方法。這樣,消費者完全控制了取消邏輯,而生產(chǎn)者只需響應(yīng)取消請求。

3. 執(zhí)行示例

public class Program
{
	public static async Task Main(string[] args)
	{
		var producer = new DataProducer();
		var consumer = new DataConsumer();
		await consumer.ConsumeDataAsync(producer.ProduceData());
	}
}

預(yù)期輸出:

[Iterator] 生成數(shù)字: 0
[Consumer] 接收到數(shù)據(jù): 0
[Iterator] 生成數(shù)字: 1
[Consumer] 接收到數(shù)據(jù): 1
[Iterator] 生成數(shù)字: 2
[Consumer] 接收到數(shù)據(jù): 2
[Iterator] 生成數(shù)字: 3
[Consumer] 接收到數(shù)據(jù): 3
[Iterator] 生成數(shù)字: 4
[Consumer] 接收到數(shù)據(jù): 4
[Trigger] 已發(fā)出取消請求
[Consumer] 數(shù)據(jù)接收已被取消

在5秒后,取消請求被觸發(fā),迭代器檢測到取消并拋出 OperationCanceledException,導致迭代過程被中斷。請注意DataConsumer在接收生產(chǎn)出來的數(shù)據(jù) IAsyncEnumerable<int> 時,已經(jīng)錯過了在生產(chǎn)函數(shù)中傳入 cancellationToken 的機會,但作為消費者,仍然可以通過 .WithCancellation 方法進行優(yōu)雅取消。

這展示了生產(chǎn)者與消費者如何通過 WithCancellation 和 [EnumeratorCancellation] 實現(xiàn)職責分離,消費者能夠獨立地控制取消邏輯,而生產(chǎn)者只需響應(yīng)取消請求。

CancellationToken 與 WithCancellation 同時作用時的行為

那么,如果在異步迭代器方法中同時傳遞了 CancellationToken 參數(shù),并通過 WithCancellation 指定了不同的取消令牌,取消操作會聽哪個的?還是都會監(jiān)聽?

結(jié)論是:兩者都會生效,只要其中任意一個取消令牌被觸發(fā),迭代器都會檢測到取消請求并中斷迭代過程。這取決于方法內(nèi)部如何處理多個取消令牌。

示例演示

以下是一個詳細的示例,展示當同時傳遞 CancellationToken 參數(shù)和使用不同的 WithCancellation 時的行為。

1. 定義異步迭代器方法

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
public class EnumeratorCancellationDemo
{
    // 異步迭代器方法,接受兩個 CancellationToken
    public async IAsyncEnumerable<int> GenerateNumbersAsync(
        [EnumeratorCancellation] CancellationToken cancellationToken,
        CancellationToken externalCancellationToken = default)
    {
        int i = 0;
        try
        {
            while (true)
            {
                // 檢查兩個取消令牌
                cancellationToken.ThrowIfCancellationRequested();
                externalCancellationToken.ThrowIfCancellationRequested();
                Console.WriteLine($"[Iterator] 生成數(shù)字: {i}");
                yield return i++;
                // 模擬異步操作
                await Task.Delay(1000, cancellationToken);
            }
        }
        finally
        {
            Console.WriteLine("[Iterator] 迭代器已退出。");
        }
	}
}

2. 定義消費者方法

public class Program
{
	static async Task Main(string[] args)
	{
		Console.WriteLine("啟動枚舉取消示例...\n");
		var demo = new EnumeratorCancellationDemo();
		// 測試1: 先取消方法參數(shù)
		Console.WriteLine("=== 測試1: 先取消方法參數(shù) ===\n");
		await TestCancellation(demo, cancelParamFirst: true);
		// 測試2: 先取消 WithCancellation
		Console.WriteLine("\n=== 測試2: 先取消 WithCancellation ===\n");
		await TestCancellation(demo, cancelParamFirst: false);
		Console.WriteLine("\n演示結(jié)束。");
		Console.ReadLine();
	}
	static async Task TestCancellation(EnumeratorCancellationDemo demo, bool cancelParamFirst)
	{
		using CancellationTokenSource ctsParam = new CancellationTokenSource();
		using CancellationTokenSource ctsWith = new CancellationTokenSource();
		if (cancelParamFirst)
		{
			// 第一個取消任務(wù):3秒后取消 ctsParam
			_ = Task.Run(async () =>
			{
				await Task.Delay(3000);
				ctsParam.Cancel();
				Console.WriteLine("[Trigger] 已取消 ctsParam (方法參數(shù))");
			});
			// 第二個取消任務(wù):5秒后取消 ctsWith
			_ = Task.Run(async () =>
			{
				await Task.Delay(5000);
				ctsWith.Cancel();
				Console.WriteLine("[Trigger] 已取消 ctsWith (WithCancellation)");
			});
		}
		else
		{
			// 第一個取消任務(wù):3秒后取消 ctsWith
			_ = Task.Run(async () =>
			{
				await Task.Delay(3000);
				ctsWith.Cancel();
				Console.WriteLine("[Trigger] 已取消 ctsWith (WithCancellation)");
			});
			// 第二個取消任務(wù):5秒后取消 ctsParam
			_ = Task.Run(async () =>
			{
				await Task.Delay(5000);
				ctsParam.Cancel();
				Console.WriteLine("[Trigger] 已取消 ctsParam (方法參數(shù))");
			});
		}
		try
		{
			// 傳遞 ctsWith.Token 作為方法參數(shù),并通過 WithCancellation 傳遞 ctsWith.Token
			await foreach (var number in demo.GenerateNumbersAsync(ctsWith.Token, ctsParam.Token).WithCancellation(ctsWith.Token))
			{
				Console.WriteLine($"[Consumer] 接收到數(shù)字: {number}");
			}
		}
		catch (OperationCanceledException ex)
		{
			string reason = ex.CancellationToken == ctsWith.Token ? "WithCancellation" : "方法參數(shù)";
			Console.WriteLine($"[Iterator] 迭代器檢測到取消。原因: {reason}");
			Console.WriteLine("[Consumer] 枚舉已被取消。");
		}
	}
}

3. 運行示例并觀察結(jié)果

啟動程序后,控制臺輸出可能如下所示:

啟動枚舉取消示例...
=== 測試1: 先取消方法參數(shù) ===
[Iterator] 生成數(shù)字: 0
[Consumer] 接收到數(shù)字: 0
[Iterator] 生成數(shù)字: 1
[Consumer] 接收到數(shù)字: 1
[Iterator] 生成數(shù)字: 2
[Consumer] 接收到數(shù)字: 2
[Trigger] 已取消 ctsParam (方法參數(shù))
[Iterator] 迭代器已退出。
[Iterator] 迭代器檢測到取消。原因: 方法參數(shù)
[Consumer] 枚舉已被取消。
=== 測試2: 先取消 WithCancellation ===
[Iterator] 生成數(shù)字: 0
[Consumer] 接收到數(shù)字: 0
[Iterator] 生成數(shù)字: 1
[Consumer] 接收到數(shù)字: 1
[Trigger] 已取消 ctsWith (WithCancellation)
[Iterator] 生成數(shù)字: 2
[Consumer] 接收到數(shù)字: 2
[Trigger] 已取消 ctsWith (WithCancellation)
[Iterator] 迭代器已退出。
[Iterator] 迭代器檢測到取消。原因: WithCancellation
[Consumer] 枚舉已被取消。
演示結(jié)束。

解釋:

  1. 測試1:先取消方法參數(shù) (ctsParam)

    • 在第3秒時,ctsParam 被取消。
    • 迭代器檢測到 externalCancellationToken 被取消,拋出 OperationCanceledException。
    • 終止迭代過程,即使 ctsWith 還未被取消。
  2. 測試2:先取消 WithCancellation (ctsWith)

    • 在第3秒時,ctsWith 被取消。
    • 迭代器檢測到 cancellationToken 被取消,拋出 OperationCanceledException
    • 終止迭代過程,即使 ctsParam 還未被取消。

關(guān)鍵點:

  • 獨立生效:無論是通過方法參數(shù)傳遞的 CancellationToken 還是通過 WithCancellation 傳遞的 CancellationToken,只要其中一個被取消,迭代器就會響應(yīng)取消請求并終止迭代。

  • 取消順序無關(guān)緊要:不論先取消哪一個取消令牌,迭代器都會正確響應(yīng)取消請求。取消操作的順序不會影響最終的效果。

總結(jié)

通過上述示例,我們深入了解了 [EnumeratorCancellation] 的必要性及其在異步迭代器中的核心作用。簡要回顧:

  • 消除警告:使用 [EnumeratorCancellation] 可以消除 Visual Studio 提示的警告,確保取消請求能夠正確傳遞給異步迭代器方法。

  • 職責分離:它實現(xiàn)了生產(chǎn)者與消費者的職責分離,使生產(chǎn)者專注于數(shù)據(jù)生成,消費者控制取消邏輯,從而提升代碼的可維護性和可復用性。

  • 靈活的取消機制:即使同時傳遞多個取消令牌,只要任意一個被取消,迭代器就會終止,提供了靈活而強大的取消控制能力。

轉(zhuǎn)自https://www.cnblogs.com/sdcb/p/18551982/why-we-need-enumerator-cancellation


該文章在 2024/11/19 8:44:55 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運作、調(diào)度、堆場、車隊、財務(wù)費用、相關(guān)報表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點,圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術(shù)的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點晴WMS倉儲管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務(wù)都免費,不限功能、不限時間、不限用戶的免費OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved