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

.NET?異步、并發(fā)與內(nèi)存管理的系統(tǒng)性認(rèn)知

 更新時(shí)間:2026年04月01日 08:48:56   作者:鄧?yán)贏I編程  
本文介紹了.NET異步編程模式的歷史演進(jìn),從APM到EAP,再到推薦使用的TAP(基于任務(wù)的異步),詳細(xì)解釋了TAP方法命名、Task生命周期、異常處理、取消令牌等規(guī)范,并提供了避免常見錯(cuò)誤的建議,感興趣的朋友跟隨小編一起看看吧

異步編程模式的演進(jìn)與 TAP 最佳實(shí)踐

.NET 的異步編程經(jīng)歷了三個(gè)時(shí)代。理解這段歷史不是為了考古,而是因?yàn)槟阍诰S護(hù)老代碼時(shí)必然會(huì)遭遇它們,理解它們才能優(yōu)雅地遷移。

模式時(shí)代標(biāo)志狀態(tài)
APM(異步編程模型).NET 1.xBeginXxx / EndXxx已淘汰
EAP(基于事件的異步).NET 2.0XxxAsync + XxxCompleted 事件遺留代碼
TAP(基于任務(wù)的異步).NET 4.0+Task / async / await推薦使用

TAP 方法的命名與簽名規(guī)范

很多人寫異步方法時(shí)忽視規(guī)范,導(dǎo)致 API 設(shè)計(jì)混亂。TAP 有一套嚴(yán)格的約定:

// ? 標(biāo)準(zhǔn)命名:方法名 + Async 后綴
public Task<int> ReadAsync(byte[] buffer, int offset, int count);
// ? 已有同名 EAP 方法時(shí),用 TaskAsync 后綴
public Task<string> GetTaskAsync(string url);
// ? 返回 void 的同步對(duì)應(yīng)版本 → 返回 Task
public Task SaveAsync(string path);
// ? 返回 T 的同步對(duì)應(yīng)版本 → 返回 Task<T>
public Task<UserDto> GetUserAsync(int userId);
// ? 避免:out/ref 參數(shù)在 TAP 中禁止使用
// 應(yīng)將多返回值包裝為 tuple 或自定義類型
public Task<(bool Success, string Error)> TryParseAsync(string input);

Task 的生命周期:一個(gè)經(jīng)常被忽視的細(xì)節(jié)

Task 有 冷任務(wù)(Cold Task) 和 熱任務(wù)(Hot Task) 之分。new Task(...) 創(chuàng)建的是冷任務(wù),需要手動(dòng)調(diào)用 Start()。但 TAP 方法返回的 Task 必須是已激活的熱任務(wù)——調(diào)用者不應(yīng)該也不需要調(diào)用 Start()。

?? 常見錯(cuò)誤

如果你在 TAP 方法內(nèi)部通過 new Task() 構(gòu)造任務(wù)后忘記調(diào)用 Start() 就返回它,調(diào)用者會(huì)陷入永久等待。始終確保返回的 Task 已處于運(yùn)行狀態(tài)。

異常處理的正確姿勢(shì)

異步方法中的異常處理有一個(gè)重要原則:參數(shù)驗(yàn)證異常應(yīng)該在 async 方法外層同步拋出,這樣調(diào)用者能立即捕獲,而不必 await 后才能發(fā)現(xiàn)錯(cuò)誤。

// ? 推薦:參數(shù)驗(yàn)證在外層同步完成
public Task<int> ProcessAsync(string input)
{
    if (input == null)
        throw new ArgumentNullException(nameof(input)); // 同步拋出
    return ProcessCoreAsync(input); // 委托給真正的 async 方法
}
private async Task<int> ProcessCoreAsync(string input)
{
    // 真正的異步工作
    var result = await DoWorkAsync(input);
    return result;
}

取消令牌與進(jìn)度報(bào)告:讓異步操作可控

寫了 3 年 .NET,你可能已經(jīng)在用 CancellationToken,但真正理解它的狀態(tài)機(jī)和設(shè)計(jì)模式的人并不多。

CancellationToken 的三種終態(tài)

   Task 狀態(tài)機(jī):
Created ──Start()──? Running

┌─────────────┼─────────────┐
▼             ▼             ▼
Canceled Faulted RanToCompletion
(取消請(qǐng)求) (未處理異常) (正常完成)
│             │             │
└─────────────┴─────────────┘
       IsCompleted = true

取消時(shí) Task 進(jìn)入 Canceled 狀態(tài),IsCompleted 返回 true,但 await 它會(huì)拋出 OperationCanceledException。

最佳實(shí)踐:在計(jì)算密集型任務(wù)中輪詢?nèi)∠?/h3>
internal Task<Bitmap> RenderAsync(
    ImageData data, CancellationToken cancellationToken)
{
    return Task.Run(() =>
    {
        var bmp = new Bitmap(data.Width, data.Height);
        for (int y = 0; y < data.Height; y++)
        {
            // 每行檢查一次取消請(qǐng)求,不要每像素都檢查(性能損耗)
            cancellationToken.ThrowIfCancellationRequested();
            for (int x = 0; x < data.Width; x++)
            {
                // 渲染像素 [x, y]
            }
        }
        return bmp;
    }, cancellationToken); // 傳入 token 以便 Task 啟動(dòng)前就取消
}

進(jìn)度報(bào)告:IProgress 的正確用法

不要用事件或回調(diào)來報(bào)告進(jìn)度——IProgress<T> 是官方推薦的模式,它能自動(dòng)處理線程同步問題(回調(diào)總是在創(chuàng)建 Progress<T> 實(shí)例的同步上下文中執(zhí)行,通常是 UI 線程)。

// 定義時(shí)接受 IProgress<T> 參數(shù)
public async Task<string[]> FindFilesAsync(
    string pattern,
    CancellationToken ct = default,
    IProgress<int> progress = null) // 允許為 null
{
    var results = new List<string>();
    int count = 0;
    await foreach (var file in EnumerateFilesAsync(pattern, ct))
    {
        results.Add(file);
        progress?.Report(++count); // null 安全調(diào)用
    }
    return results.ToArray();
}
// 調(diào)用端:Progress<T> 捕獲 UI 線程的同步上下文
var reporter = new Progress<int>(count =>
    progressBar.Value = count); // 這里可以安全更新 UI
await FindFilesAsync("*.cs", ct, reporter);

?? 設(shè)計(jì)建議

如果某個(gè)方法不支持取消,不要提供接受 CancellationToken 的重載——這會(huì)誤導(dǎo)調(diào)用者。反之,如果支持取消,應(yīng)當(dāng)始終提供帶 token 的重載。

任務(wù)并行庫(TPL)與 Parallel 編程

并行編程最大的陷阱是分不清 CPU 密集型 和 I/O 密集型 任務(wù),用錯(cuò)了工具反而更慢。

?? 核心原則

CPU 密集型用 Task.Run() 分發(fā)到線程池;I/O 密集型用 async/await + TaskCompletionSource,不應(yīng)綁定線程。

父子任務(wù)與 DenyChildAttach

使用第三方庫時(shí),如果對(duì)方內(nèi)部用 TaskCreationOptions.AttachedToParent 創(chuàng)建任務(wù),會(huì)導(dǎo)致你的父任務(wù)必須等待所有子任務(wù)完成——即使你不需要這種行為。使用 DenyChildAttach 可以隔離這種副作用。

// ? 默認(rèn)行為:第三方 Widget 內(nèi)部創(chuàng)建的子任務(wù)會(huì)延遲父任務(wù)完成
Task<Task> runWidget = Task.Factory.StartNew(
    () => thirdPartyWidget.Run());  // 子任務(wù) sleep 5s,父任務(wù)也等 5s
// ? 正確做法:DenyChildAttach 隔離第三方任務(wù)
Task<Task> runWidget = Task.Factory.StartNew(
    () => thirdPartyWidget.Run(),
    TaskCreationOptions.DenyChildAttach);  // 父任務(wù)立即完成

Task.WhenAll vs Task.WhenAny 的適用場(chǎng)景

// WhenAll:等待所有任務(wù)完成(并行 I/O 的核心武器)
var tasks = urls.Select(url => httpClient.GetStringAsync(url));
string[] results = await Task.WhenAll(tasks);
// WhenAny:哪個(gè)先完成用哪個(gè)(超時(shí)控制的經(jīng)典寫法)
var dataTask = FetchDataAsync();
var timeout = Task.Delay(TimeSpan.FromSeconds(5));
var winner = await Task.WhenAny(dataTask, timeout);
if (winner == timeout)
    throw new TimeoutException("請(qǐng)求超時(shí)");
return await dataTask; // 再次 await 以解包異常

Task.FromResult 優(yōu)化緩存命中

當(dāng)異步方法能從緩存直接返回時(shí),創(chuàng)建真正的異步操作是不必要的開銷。Task.FromResult() 返回一個(gè)已完成的任務(wù),零開銷。

private static readonly ConcurrentDictionary<string, string>
    _cache = new();
public static Task<string> DownloadStringAsync(string url)
{
    // 緩存命中:返回已完成的 Task,無線程切換開銷
    if (_cache.TryGetValue(url, out string? cached))
        return Task.FromResult(cached);
    // 緩存未命中:真正的異步下載
    return Task.Run(async () =>
    {
        var content = await _httpClient.GetStringAsync(url);
        _cache.TryAdd(url, content);
        return content;
    });
}

線程安全集合的選型與陷阱

System.Collections.Concurrent 命名空間提供了幾個(gè)高性能線程安全集合,但選錯(cuò)了反而不如加鎖的普通集合性能好。

集合類型適用場(chǎng)景注意事項(xiàng)
ConcurrentDictionary多線程頻繁讀寫鍵值對(duì)GetOrAdd / AddOrUpdate 非原子操作
ConcurrentQueueFIFO 生產(chǎn)者-消費(fèi)者場(chǎng)景枚舉不保證順序穩(wěn)定
BlockingCollection有界緩沖 + 阻塞語義需要配合 CompleteAdding() 正確關(guān)閉
ConcurrentBag混合生產(chǎn)者-消費(fèi)者(同線程添加取出)純生產(chǎn)消費(fèi)場(chǎng)景比其他集合慢

ConcurrentDictionary 的非原子陷阱

這是很多人犯錯(cuò)的地方:ConcurrentDictionary 的所有單個(gè)方法是線程安全的,但復(fù)合操作("檢查-然后-添加")不是原子的。

// ?? 注意:valueFactory 可能被多個(gè)線程調(diào)用
// 但只有一個(gè)線程的結(jié)果會(huì)被保留
var value = dict.GetOrAdd(key, k =>
{
    // 這里的代碼可能被并發(fā)執(zhí)行多次!
    // 如果 factory 有副作用(如 DB 寫入),需要額外處理
    return new ExpensiveObject(k);
});
// ? 如果 factory 有副作用,使用 Lazy<T> 確保只執(zhí)行一次
var lazy = dict.GetOrAdd(key, k =>
    new Lazy<ExpensiveObject>(() => new ExpensiveObject(k)));
var obj = lazy.Value; // 真正的構(gòu)造只發(fā)生一次

BlockingCollection:生產(chǎn)者-消費(fèi)者管道

var queue = new BlockingCollection<WorkItem>(boundedCapacity: 100);
// 生產(chǎn)者
Task producer = Task.Run(() =>
{
    foreach (var item in GetWorkItems())
        queue.Add(item);
    queue.CompleteAdding(); // ?? 必須調(diào)用!否則消費(fèi)者永遠(yuǎn)阻塞
});
// 消費(fèi)者:GetConsumingEnumerable 會(huì)在 CompleteAdding 后自動(dòng)退出
Task consumer = Task.Run(() =>
{
    foreach (var item in queue.GetConsumingEnumerable())
        ProcessItem(item);
});
await Task.WhenAll(producer, consumer);

P/Invoke 與 Native 互操作

P/Invoke 是調(diào)用 Windows API 或 C 庫的標(biāo)準(zhǔn)方式,但很多 .NET 開發(fā)者很少接觸。理解它的基本原理能幫你在需要時(shí)快速上手,也能讀懂底層庫的代碼。

最小化示例:調(diào)用 Windows API

using System.Runtime.InteropServices;
public static class NativeMethods
{
    // DllImport 聲明:映射到 kernel32.dll 中的函數(shù)
    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern bool CreateDirectory( 
        string lpPathName,
        IntPtr lpSecurityAttributes);
    // 現(xiàn)代寫法(.NET 7+):LibraryImport + Source Generator(更快,AOT 友好)
    [LibraryImport("kernel32.dll", SetLastError = true,
        StringMarshalling = StringMarshalling.Utf16)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static partial bool CreateDirectoryModern( 
        string lpPathName,
        IntPtr lpSecurityAttributes);
}

最危險(xiǎn)的陷阱:委托被 GC 回收

將委托轉(zhuǎn)換為函數(shù)指針傳給 Native 代碼后,.NET GC 不知道 Native 代碼還在使用這個(gè)指針。如果委托對(duì)象被回收,程序會(huì)崩潰。

// ? 危險(xiǎn):委托可能在 Native 調(diào)用期間被回收
NativeMethods.RegisterCallback(
    Marshal.GetFunctionPointerForDelegate(
        new MyCallback(OnEvent))); // 匿名委托,無引用!
// ? 正確:持有委托的引用直到 Native 不再使用
private readonly MyCallback _callback = OnEvent; // 類級(jí)別字段
void Init()
{
    var fnPtr = Marshal.GetFunctionPointerForDelegate(_callback);
    NativeMethods.RegisterCallback(fnPtr);
    GC.KeepAlive(_callback); // 明確告知 GC 此對(duì)象不可回收
}

?? 跨平臺(tái)注意

C/C++ 的 long 在 Windows 上是 32 位,在 macOS/Linux 上是 64 位??缙脚_(tái)時(shí)應(yīng)使用 .NET 6+ 提供的 CLong / CULong 類型,而不是 int 或 C# 的 long。

內(nèi)存管理:GC、Dispose 與非托管資源

"C# 有 GC,不用管內(nèi)存" 是一個(gè)危險(xiǎn)的誤解。非托管資源(文件句柄、數(shù)據(jù)庫連接、網(wǎng)絡(luò)套接字)GC 不會(huì)自動(dòng)釋放,這是絕大多數(shù)內(nèi)存泄漏的根源。

標(biāo)準(zhǔn) Dispose 模式

持有非托管資源的類必須實(shí)現(xiàn) IDisposable。以下是經(jīng)典實(shí)現(xiàn)模式:

public class ResourceHolder : IDisposable
{
    private IntPtr _nativeHandle;     // 非托管資源
    private Stream _managedStream;    // 托管的 IDisposable
    private bool _disposed = false;
    // 公共方法:供調(diào)用方手動(dòng)釋放
    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this); // 告知 GC 不必再調(diào)用析構(gòu)函數(shù)
    }
    // 核心釋放邏輯
    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;
        if (disposing)
        {
            // 釋放托管資源(只在主動(dòng) Dispose 時(shí))
            _managedStream?.Dispose();
        }
        // 釋放非托管資源(無論哪種路徑都要釋放)
        if (_nativeHandle != IntPtr.Zero)
        {
            NativeFree(_nativeHandle);
            _nativeHandle = IntPtr.Zero;
        }
        _disposed = true;
    }
    // 析構(gòu)函數(shù):GC 兜底(不能保證調(diào)用時(shí)機(jī))
    ~ResourceHolder() => Dispose(disposing: false);
}

異步 Dispose:IAsyncDisposable

.NET Core 3.0+ 引入了 IAsyncDisposable,用于需要異步釋放資源的場(chǎng)景(如關(guān)閉網(wǎng)絡(luò)連接需要發(fā)送 FIN 包)。配合 await using 語法使用:

public class AsyncConnection : IAsyncDisposable
{
    private readonly NetworkStream _stream;
    public async ValueTask DisposeAsync()
    {
        await _stream.FlushAsync();  // 異步刷新緩沖區(qū)
        await _stream.DisposeAsync(); // 異步關(guān)閉連接
    }
}
// await using 確保無論是否異常都會(huì)調(diào)用 DisposeAsync
await using var conn = new AsyncConnection(endpoint);
await conn.SendAsync(data);

?? 性能提示

返回 ValueTask 而不是 Task 可以在同步完成的情況下避免堆分配。當(dāng)你的異步方法大多數(shù)時(shí)候能同步完成(如緩存命中)時(shí),ValueTask 能顯著提升性能。

寫出健壯異步代碼的自查清單

在提交 PR 之前,不妨過一遍這份清單:

  • TAP 方法名以 Async 結(jié)尾,返回 Task 或 Task<T>
  • 參數(shù)驗(yàn)證在 async 方法外層同步完成,不被包裹進(jìn) Task
  • 接受 CancellationToken 的方法在循環(huán)或 I/O 前檢查取消狀態(tài)
  • 返回 Task 的方法不在同步路徑上長(zhǎng)時(shí)間阻塞
  • 沒有 .Result 或 .Wait()(ASP.NET 環(huán)境中極易死鎖)
  • 持有非托管資源的類實(shí)現(xiàn)了 IDisposable,并在 Dispose(false) 中釋放非托管部分
  • 向 Native 代碼傳遞的委托有足夠長(zhǎng)的生命周期(類字段或 GC.KeepAlive
  • 使用 BlockingCollection 時(shí)生產(chǎn)者最終調(diào)用了 CompleteAdding()
  • 并發(fā)訪問 ConcurrentDictionary 的復(fù)合操作用了適當(dāng)?shù)脑臃椒ɑ蜴i
  • async void 僅用于事件處理器,其他任何地方都應(yīng)返回 Task

到此這篇關(guān)于.NET 進(jìn)階之路:異步、并發(fā)與內(nèi)存管理的系統(tǒng)性認(rèn)知的文章就介紹到這了,更多相關(guān).NET 進(jìn)階之路:異步、并發(fā)與內(nèi)存管理的系統(tǒng)性認(rèn)知內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

武义县| 内丘县| 若羌县| 永福县| 甘谷县| 资讯 | 曲靖市| 台北县| 长治市| 尚义县| 大埔区| 牟定县| 资中县| 田东县| 远安县| 江都市| 伽师县| 绵竹市| 绥中县| 子洲县| 东兴市| 磴口县| 大新县| 抚顺县| 扎囊县| 长丰县| 福贡县| 临江市| 梁河县| 汪清县| 古丈县| 尚义县| 凤台县| 忻城县| 千阳县| 平顺县| 繁昌县| 新丰县| 余庆县| 天气| 长丰县|