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

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

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

異步編程模式的演進與 TAP 最佳實踐

.NET 的異步編程經(jīng)歷了三個時代。理解這段歷史不是為了考古,而是因為你在維護老代碼時必然會遭遇它們,理解它們才能優(yōu)雅地遷移。

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

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

很多人寫異步方法時忽視規(guī)范,導致 API 設計混亂。TAP 有一套嚴格的約定:

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

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

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

?? 常見錯誤

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

異常處理的正確姿勢

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

// ? 推薦:參數(shù)驗證在外層同步完成
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;
}

取消令牌與進度報告:讓異步操作可控

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

CancellationToken 的三種終態(tài)

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

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

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

最佳實踐:在計算密集型任務中輪詢?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++)
        {
            // 每行檢查一次取消請求,不要每像素都檢查(性能損耗)
            cancellationToken.ThrowIfCancellationRequested();
            for (int x = 0; x < data.Width; x++)
            {
                // 渲染像素 [x, y]
            }
        }
        return bmp;
    }, cancellationToken); // 傳入 token 以便 Task 啟動前就取消
}

進度報告:IProgress 的正確用法

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

// 定義時接受 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);

?? 設計建議

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

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

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

?? 核心原則

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

父子任務與 DenyChildAttach

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

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

Task.WhenAll vs Task.WhenAny 的適用場景

// WhenAll:等待所有任務完成(并行 I/O 的核心武器)
var tasks = urls.Select(url => httpClient.GetStringAsync(url));
string[] results = await Task.WhenAll(tasks);
// WhenAny:哪個先完成用哪個(超時控制的經(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("請求超時");
return await dataTask; // 再次 await 以解包異常

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

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

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 命名空間提供了幾個高性能線程安全集合,但選錯了反而不如加鎖的普通集合性能好。

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

ConcurrentDictionary 的非原子陷阱

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

// ?? 注意:valueFactory 可能被多個線程調(diào)用
// 但只有一個線程的結果會被保留
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; // 真正的構造只發(fā)生一次

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

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)用!否則消費者永遠阻塞
});
// 消費者:GetConsumingEnumerable 會在 CompleteAdding 后自動退出
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 庫的標準方式,但很多 .NET 開發(fā)者很少接觸。理解它的基本原理能幫你在需要時快速上手,也能讀懂底層庫的代碼。

最小化示例:調(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);
}

最危險的陷阱:委托被 GC 回收

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

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

?? 跨平臺注意

C/C++ 的 long 在 Windows 上是 32 位,在 macOS/Linux 上是 64 位。跨平臺時應使用 .NET 6+ 提供的 CLong / CULong 類型,而不是 int 或 C# 的 long

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

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

標準 Dispose 模式

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

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

異步 Dispose:IAsyncDisposable

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

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

?? 性能提示

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

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

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

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

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

相關文章

最新評論

长武县| 齐齐哈尔市| 咸宁市| 辽中县| 濉溪县| 什邡市| 肥乡县| 都江堰市| 密云县| 定州市| 四川省| 北海市| 阜新市| 衡阳县| 南安市| 前郭尔| 揭东县| 南丰县| 镇安县| 建瓯市| 罗平县| 大洼县| 竹溪县| 米脂县| 喀喇沁旗| 休宁县| 桐庐县| 白银市| 大足县| 大英县| 玛沁县| 额济纳旗| 扎兰屯市| 泰州市| 江源县| 桦南县| 嵊州市| 锡林郭勒盟| 抚州市| 禄丰县| 高雄市|