最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C# 異步回調(diào)與等待機(jī)制全解

 更新時(shí)間:2026年04月03日 09:33:41   作者:MutoKazuo  
文章介紹了ManualResetEvent和AutoResetEvent的特性、適用場(chǎng)景以及與TaskCompletionSource和SemaphoreSlim等現(xiàn)代異步方法的對(duì)比,對(duì)C# 異步回調(diào)與等待機(jī)制相關(guān)知識(shí)感興趣的朋友跟隨小編一起看看吧

TaskCompletionSource (TCS)

一個(gè)可以手動(dòng)控制狀態(tài)的 Task。它允許你創(chuàng)建一個(gè)任務(wù),并在稍后的某個(gè)時(shí)間點(diǎn)手動(dòng)將其標(biāo)記為“已完成”。

Task.WhenAny

超時(shí)處理機(jī)制。通過將“結(jié)果任務(wù)”和“延遲任務(wù)(Task.Delay)”放在一起競(jìng)爭(zhēng),確保程序不會(huì)因?yàn)橄G失而永久死鎖。

private TaskCompletionSource<string> _transTaskSource;
private TaskCompletionSource<bool> _transAviResultSource;
public bool TransResult{get;set;}
public string ImageFileName{get;set;}
private void OnAppMessageReceived(AppMessageEventArgs obj)
{
    if (obj.MessageId == "xxx")
    {
        Dictionary<string, object> dic = JsonHelper.Deserialize<Dictionary<string, object>>(obj.Value);
        if (dic["FileName"].ToString().Contains("avi"))
        {
            bool result = dic["Result"].ToString() == "1";
            _transAviResultSource?.TrySetResult(result);
        }
        else
        {
            string fileName = dic["FileName"].ToString();
            _transTaskSource?.TrySetResult(fileName);
        }
    }
}
protected virtual async Task<string> OnTransStandard(string fileFormat)
{
	switch (fileFormat)
    {
		case "avi":
			_transAviResultSource = new TaskCompletionSource<bool>();
			var result = _transService.Trans2Avi();
			var timeoutTask = Task.Delay(TimeSpan.FromSeconds(15));
			var completedTask = await Task.WhenAny(_transAviResultSource.Task, timeoutTask);
			if (completedTask == _transAviResultSource.Task)
			{
				TransResult = result;
			}
		break;
		default:
			_trasnTaskSource = new TaskCompletionSource<string>();
            _transService.Trans();
            var timeoutTask = Task.Delay(TimeSpan.FromSeconds(30), cancellationToken);
            var completedTask = await Task.WhenAny(_trasnTaskSource.Task, timeoutTask);
            if (completedTask == _trasnTaskSource.Task)
            {
                ImageFileName = await _trasnTaskSource.Task;
            }
            else
            {
                // TODO
            }
		break;
	}
}

優(yōu)化版本

using System.Collections.Concurrent;
// 使用字典支持并發(fā),Key 為唯一標(biāo)識(shí)(如文件名或請(qǐng)求ID)
private readonly ConcurrentDictionary<string, TaskCompletionSource<string>> _transTasks = new();
private readonly ConcurrentDictionary<string, TaskCompletionSource<bool>> _aviTasks = new();
private void OnAppMessageReceived(AppMessageEventArgs obj)
{
    if (obj.MessageId != "xxx") return;
    var dic = JsonHelper.Deserialize<Dictionary<string, object>>(obj.Value);
    string fileName = dic["FileName"]?.ToString() ?? string.Empty;
    if (fileName.Contains("avi"))
    {
        bool result = dic["Result"]?.ToString() == "1";
        // 嘗試從字典中移除并設(shè)置結(jié)果,確保只處理一次
        if (_aviTasks.TryRemove(fileName, out var tcs))
        {
            tcs.TrySetResult(result);
        }
    }
    else
    {
        if (_transTasks.TryRemove(fileName, out var tcs))
        {
            tcs.TrySetResult(fileName);
        }
    }
}
protected virtual async Task<string> OnTransStandard(string fileFormat, string fileName)
{
    // 1. 設(shè)置超時(shí)取消令牌
    using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(fileFormat == "avi" ? 15 : 30));
    try
    {
        if (fileFormat == "avi")
        {
            var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
            _aviTasks[fileName] = tcs;
            _transService.Trans2Avi();
            // 使用 Register 綁定取消令牌到 TCS
            using (cts.Token.Register(() => tcs.TrySetCanceled()))
            {
                TransResult = await tcs.Task;
                return fileName;
            }
        }
        else
        {
            var tcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
            _transTasks[fileName] = tcs;
            _transService.Trans();
            using (cts.Token.Register(() => tcs.TrySetCanceled()))
            {
                ImageFileName = await tcs.Task;
                return ImageFileName;
            }
        }
    }
    catch (OperationCanceledException)
    {
        // 統(tǒng)一處理超時(shí)邏輯
        HandleTimeout(fileName, fileFormat);
        return string.Empty;
    }
    finally
    {
        // 確保清理字典,防止內(nèi)存泄漏
        _aviTasks.TryRemove(fileName, out _);
        _transTasks.TryRemove(fileName, out _);
    }
}

同步阻塞式的等待機(jī)制(同步原語)

傳統(tǒng)的線程同步對(duì)象 ManualResetEvent 來強(qiáng)制當(dāng)前線程“停下”執(zhí)行,直到收到特定的信號(hào)。

調(diào)用 OnDataTransformation 的線程會(huì)被物理掛起,不消耗 CPU 周期,但會(huì)占用一個(gè)線程資源,直到 _event.Set() 被調(diào)用。

特性ManualResetEvent (手動(dòng))AutoResetEvent (自動(dòng))
放行數(shù)量調(diào) Set() 后,所有正在等待的線程都會(huì)被放行。調(diào)用 Set() 后,僅有一個(gè)線程會(huì)被放行。
復(fù)位方式必須手動(dòng)調(diào)用 Reset() 才會(huì)再次阻塞線程。只要有一個(gè)線程通過,它會(huì)自動(dòng)回到阻塞狀態(tài)。
典型場(chǎng)景廣播/通知:一個(gè)信號(hào)通知多個(gè)任務(wù)同時(shí)開始。同步/排隊(duì):確保對(duì)某個(gè)資源的訪問是串行的。
public bool TransResult { get; set; }
public string Filenames { get; set; }
private static readonly ManualResetEvent _event = new ManualResetEvent(false);
private void OnAppMessageReceived(AppMessageEventArgs obj)
{
    if (obj.MessageId == "xxx")
    {
        Dictionary<string, object> dic = JsonHelper.Deserialize<Dictionary<string, object>>(obj.Value);
        if (dic["FileName"].ToString().Contains("avi"))
        {
            TransResult = dic["Result"].ToString() == "1";
            _event.Set();
        }
        else
        {
            Filenames = dic["FileName"].ToString();
            _event.Set();
        }
    }
}
public virtual void OnDataTransformation()
{
	cancellationToken.ThrowIfCancellationRequested();
	_TranService.TransImage();
    _event.Reset();
    _event.WaitOne();
	cancellationToken.ThrowIfCancellationRequested();
}

選擇 ManualResetEvent 還是 AutoResetEvent 取決于你的控制目標(biāo):是想給“一群人”發(fā)信號(hào),還是想讓“一個(gè)人”過閘機(jī)。

1. ManualResetEvent(手動(dòng)重置)

適用場(chǎng)景:狀態(tài)通知 / 廣播 (Broadcasting)

它通常用于表示一個(gè)“開關(guān)”或“階段完成”的狀態(tài)。一旦這個(gè)狀態(tài)達(dá)成了,所有依賴它的線程都可以繼續(xù)。

典型例子:系統(tǒng)初始化

  • 你的主程序啟動(dòng)時(shí)有多個(gè)后臺(tái)服務(wù)(數(shù)據(jù)庫連接、緩存加載、配置文件讀?。?。
    • 主線程調(diào)用 _initEvent.WaitOne()。
    • 只有當(dāng)所有初始化工作全部完成后,才調(diào)用一次 Set()
    • 此時(shí),所有卡在 WaitOne 處的邏輯都會(huì)同時(shí)被釋放。

典型例子:暫停/恢復(fù)功能

  • 在下載器或播放器中,按下“暫停”即 Reset()(關(guān)閘),所有下載線程 WaitOne;按下“開始”即 Set()(開閘),所有線程同時(shí)恢復(fù)工作。

2. AutoResetEvent(自動(dòng)重置)

適用場(chǎng)景:獨(dú)占資源 / 生產(chǎn)者-消費(fèi)者 (Queueing)

它通常用于確保任務(wù)的串行化執(zhí)行,或者作為簡(jiǎn)單的線程間信號(hào)傳遞。

典型例子:任務(wù)隊(duì)列(單線程處理)

  • 你有一個(gè)后臺(tái)線程專門處理發(fā)郵件的操作。
    • 當(dāng)有新郵件進(jìn)入隊(duì)列時(shí),調(diào)用一次 Set()。
    • 后臺(tái)處理線程 WaitOne() 收到信號(hào),起來發(fā)一封郵件。
    • 發(fā)完后,由于是 AutoReset,它會(huì)自動(dòng)回到阻塞狀態(tài),等待下一次 Set()。

典型例子:由于硬件限制的互斥訪問

  • 像你代碼中這種“發(fā)送指令 -> 等待硬件回傳”的模式。如果你想確保發(fā)一個(gè)收一個(gè),且不希望第二個(gè)指令在第一個(gè)指令沒返回前就跑掉,AutoResetEvent 更安全,因?yàn)樗幚硗暌淮螘?huì)自動(dòng)“關(guān)門”。

3. 對(duì)比

維度ManualResetEventAutoResetEvent
形象比喻大門的閘刀開關(guān):拉上去,所有人都能進(jìn);拉下來,所有人都得停。地鐵的旋轉(zhuǎn)閘機(jī):刷一次卡(Set),只能進(jìn)去一個(gè)人,進(jìn)去后閘機(jī)立刻鎖死。
核心邏輯狀態(tài)驅(qū)動(dòng):側(cè)重于“某個(gè)條件是否達(dá)成”。事件驅(qū)動(dòng):側(cè)重于“某個(gè)動(dòng)作是否發(fā)生”。
重置時(shí)機(jī)你認(rèn)為這個(gè)狀態(tài)不再有效時(shí)(手動(dòng))。線程穿過 WaitOne 的那一刻(自動(dòng))。

4. 為什么用得少了?

在高性能開發(fā)中,這兩個(gè)類正逐漸被以下方案取代:

  1. TaskCompletionSource (TCS)
    1. 理由:它是異步非阻塞的(Async/Non-blocking)。前面的 Manual/Auto 都會(huì)死死占住一個(gè)操作系統(tǒng)線程,非常浪費(fèi)資源。
  2. SemaphoreSlim (信號(hào)量)
    1. 理由:它比 AutoResetEvent 更強(qiáng)大。它支持 WaitAsync(異步等待),而且可以控制允許 N 個(gè)線程同時(shí)通過,而不僅僅是一個(gè)。
  3. ManualResetEventSlim
    1. 理由:如果你非要用同步等待,請(qǐng)優(yōu)先使用帶 Slim 后綴的版本。它在等待時(shí)間很短時(shí)會(huì)先進(jìn)行“自旋(Spin)”,不直接切換到昂貴的內(nèi)核模式,性能更好。

異步等待 (Await) vs 同步掛起 (Wait)

維度TaskCompletionSourceManual/AutoResetEvent
編程模型異步 (Asynchronous)。基于 Task,符合現(xiàn)代 .NET 開發(fā)習(xí)慣。同步 (Synchronous)?;诓僮飨到y(tǒng)的內(nèi)核對(duì)象。
線程利用率。await 時(shí)線程會(huì)釋放回線程池,去處理其他任務(wù)。。當(dāng)前線程被徹底卡死(Block),什么都干不了。
超時(shí)處理靈活。通過 Task.Delay 或 CancellationToken 輕松實(shí)現(xiàn)。較硬。需給 WaitOne(timeout) 傳參,且寫法略顯臃腫。
UI 響應(yīng)性友好。如果在 UI 線程調(diào)用,界面不會(huì)卡死。危險(xiǎn)。如果在 UI 線程調(diào)用,界面會(huì)直接崩潰/無響應(yīng)(Deadlock)。
并發(fā)支持較好。通過 tcs 實(shí)例可以區(qū)分不同的請(qǐng)求。。static 的 _event 意味著全局只能同時(shí)處理一個(gè)任務(wù)。

第一種 (TaskCompletionSource) —— 手機(jī)短信提醒: 你該干嘛干嘛(去洗澡、打游戲),線程被釋放回去了??爝f到了,手機(jī)響了(Task 完成),你再回到門口處理快遞。這種是非阻塞的。

第二種 (ManualResetEvent) —— 站在門口死等: 你推掉了一切活動(dòng),就站在門口盯著路口(線程被掛起)。快遞員沒來,你哪也不去,也不說話??爝f員一招手(Set),你立刻動(dòng)起來。這種是同步阻塞的。

同步方式 (EventWaitHandle)

// 這是一條死胡同,除非有人開門,否則車(線程)就停死在這里
_event.WaitOne();
// 只有開門后,才能跑這一行
DoNextStep();
  • 后果:如果你在 UI 線程(如點(diǎn)擊按鈕)里這么寫,你的軟件界面會(huì)直接“未響應(yīng)”,因?yàn)?UI 線程被阻塞了,沒法處理鼠標(biāo)點(diǎn)擊和界面刷新。

異步方式 (TaskCompletionSource)

// 這是一條路口,車(線程)發(fā)現(xiàn)紅燈,就先掉頭去干別的活了
await _tcs.Task;
// 綠燈亮了(SetResult),會(huì)有另一輛車(或原車)回來繼續(xù)跑
DoNextStep();
  • 后果:UI 依然絲滑。await 釋放了當(dāng)前線程,讓它回消息循環(huán)里去處理界面繪制,等結(jié)果到了再回來。

.NET中的位置

在 .NET 專門的同步分類中,它們屬于內(nèi)核模式同步對(duì)象(Kernel-mode objects)。

類別代表組件特點(diǎn)
內(nèi)核模式 (同步)ManualResetEvent, AutoResetEvent, Mutex重型。涉及操作系統(tǒng)內(nèi)核切換,跨進(jìn)程可用,但性能開銷大。
混合模式 (同步)ManualResetEventSlim, SemaphoreSlim輕量。先自旋再掛起,性能極高,是現(xiàn)代同步的首選。
異步模式TaskCompletionSource, Task.WhenAny現(xiàn)代。完全不阻塞線程,支撐高并發(fā)的核心。

到此這篇關(guān)于C# 異步回調(diào)與等待機(jī)制全解的文章就介紹到這了,更多相關(guān)C# 異步回調(diào)與等待機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

个旧市| 拉孜县| 稻城县| 丰都县| 高平市| 龙门县| 宣恩县| 思茅市| 阳朔县| 河源市| 绥阳县| 长治市| 平潭县| 剑河县| 陕西省| 通山县| 平罗县| 诏安县| 庆阳市| 万全县| 安丘市| 黔南| 五莲县| 奇台县| 上饶县| 赣州市| 德阳市| 通化县| 天津市| 从江县| 广东省| 松阳县| 大冶市| 云和县| 九寨沟县| 东阳市| 阜南县| 清苑县| 凤山县| 邹平县| 云阳县|