只有累積,沒有奇蹟

2025年3月8日 星期六

[NET] 移除未使用的 ASP.NET SDK 版本

前言
最近筆電空間告急,在 Google 解決方案時發現過去自己有撰寫過一篇文章是 移除未使用的 ASP.NET Core SDK,但仔細看完內容加上已經過了五年的時間,深深覺得五年過去會有更好的解決方法,於是乎終於讓我找到更有效的方法可以移除未使用的 dotnet framework 版本,這篇就跟大家簡單分享,與若對於上述內容有問題或是不清楚的地方,歡迎提出來一起討論。

查看 SDK 版本
首先,我們來透過指令來看目前電腦有安裝哪些版本 SDK (chatGPT 用多了,在用字描述上都不自覺用首先...其次 XDDDD)
dotnet --list-sdks 
透過此指令可以查詢到目前電腦中安裝的 SDK 版本與安裝路徑位置
如果想要查看 runtime 則可以輸入
dotnet --list-runtimes
透過上述方式看到目前電腦所安裝的 Runtime 與 SDK 版本,如果長期使用 dotnet 開發的朋友們可能也知道安裝時會在固定位置,例如
執行檔 : C:\program files\dotnet\dotnet.exe
.NET SDK : C:\program files\dotnet\sdk\版號
.NET Runtime : C:\program files\dotnet\shared\{runtime-type}\版號\

解法 : Windows 內建移除
如果是要移除單一的應用程式,一般可以到 應用程式 > 已安裝的應用程式 頁面來移除 .NET SDK 與 Runtime,
解法 : dotnet-core-uninstall
但上述做法只適用於 SDK 量比較少的狀況,如果你電腦使用一段時間比較多的時候要逐一移除就會比較辛苦跟麻煩,這次主要是分享的方法是微軟官方提供在 2020 年所提供的新方法,提供一個好用的工具,將系統中的 SDK 進行控制與清理的動作,只保留自己在開發時所需版本的 runtime 和 SDK。

工具下載連結 : https://github.com/dotnet/cli-lab/releases

接著往下滑到 asset 區域,我所使用的 OS 是 windows 因此選擇下載 dotnet-core-unistall.msi 檔案
執行安裝下載的檔案,以目前最新版為例是 1.7.550802,安裝完畢後,開啟命令提示字元輸入 dotnet-core-uninstall
可以看到所支援的 command 如下
list : List .NET Core SDKs or Runtimes that can be removed with this tool.
 dry-run, whatif  : Display .NET Core SDKs and Runtimes that will be removed.
 remove  : Remove the specified .NET Core SDKs or Runtimes.
該程式在移除上還是有提醒工具的限制
This tool cannot uninstall versions of the runtime or SDK that are?
    - SDKs installed using Visual Studio 2019 Update 3 or later.
    - SDKs and runtimes installed via zip/scripts.
    - Runtimes installed with SDKs (these should be removed by removing that SDK).
小提醒 : 在使用上還是記得開啟執行程式 cmd or powershell 前,需要使用系統管理員權限才能夠移除,權限不夠會出現 The current user does not have adequate privileges.提示

找出電腦所安裝的 SDK
輸入 dotnet-core-uninstall list 指令,可以透過程式找到目前電腦所安裝的 SDK 清單
移除 SDK
移除時請輸入指令 dotnet-core-uninstall remove 加上特定版本,例如上面看到目前電腦中有 7.0 & 8.0 版本,要移除時請輸入完整版號與指令,例如 dotnet-core-uninstall remove 7.0.410 --sdk 才會生效,確定後在下 Yes 即可

小結
以上快速介紹了使用微軟所提供的工具來刪除所安裝的 SDK,希望可以有機會幫到跟我一樣困擾的夥伴們,happy Coding !

參考
cli-lab

2025年2月8日 星期六

[NET] 探索 OpenTelemetry Auto-Instrumentation 的核心技術

前言
Observability 是手段,不是目的。透過 Auto-Instrumentation,我們更接近「讓系統自己說話」的願景。

在 COSCUP 2024 我有幸(不要臉) 再次參與並分享這一年來我在可觀測性實踐上最有趣的內容:「OpenTelemetry 的 Auto-Instrumentation 技術核心與應用實戰」。這篇文章,我會完整拆解 OpenTelemetry SDK 的 Auto-Instrumentation 的原理、架構、實務流程,並從開發者的視角出發,帶你理解什麼是「讓系統自己說話」,希望可以讓想要了解 OTel SDK 底層細節實作的朋友有幫助。


OpenTelemetry 是什麼

OpenTelemetry(簡稱 OTel)是 CNCF(Cloud Native Computing Foundation)旗下的觀測性開源計畫。它整合了 OpenTracing 和 OpenCensus,成為雲原生時代的統一觀測標準。

它主要處理三件事:

  • Instrument:幫助你從應用中收集觀測訊號(Signals)
  • OTLP:無論是 Trace、Metric 還是 Log,都有標準格式
  • Export:支援多種後端(如 Jaeger、Zipkin、Grafana Tempo、Prometheus...)

OpenTelemetry 是目前 CNCF 第二活躍的專案(僅次於 Kubernetes),也是現代 Observability 的標準。


Code-based v.s zero-code

讓程式「可以被觀察」,首先就要「儀表化」程式碼(Instrumentation)。這裡透過一張表整理 Code-based 與 zero-code 兩者的差異,更重要的是實際應用場景

小小建議:「Auto-Instrumentation 優先、Manual 優化補強」

Auto-Instrumentation 設計願景

根據 OpenTelemetry .NET Auto-Instrumentation 官方設計文件,這個 SDK 是以五大設計願景(Vision)為核心來開發的,每一個願景都對應到實務中我們真正關心的「導入門檻」、「維運穩定」、「擴充能力」。

高效能(High Performance)
  • 使用 IL 重寫,不改原始碼
  • Lazy Loading 降低資源開銷
  • 非同步匯出 Trace 資料
開箱即用(Useful by Default)
  • 內建常見欄位與框架支援
  • 啟動即生效
穩定可靠(Reliable)
  • 錯誤不影響應用
  • 具容錯能力與 fallback
可見(Visible)
  • 啟動時列出已啟用套件
  • 支援 metrics 與 debug mode
可擴展(Extensible)
  • 插件化結構,支援自定義與社群套件

Auto-Instrumentation 架構與元件
Application & Runtime(應用與執行環境)
  • Application IL(中間語言)
    .NET 應用程式編譯後的中間碼,是 Auto-Instrumentation 插入追蹤邏輯的目標對象。
  • CLR(Common Language Runtime)
    .NET 的執行核心,提供 ICorProfilerCallback / ICorProfilerInfo 介面供原生 Profiler 攔截執行事件與注入邏輯。
OTel CLR Profiler(Native)
  • 職責: 掛載在 CLR 上,透過 ICorProfiler API 攔截應用啟動流程。
  • 功能: 將 Bytecode Instrumentation 插入 Application IL,實現 Zero-code Observability。
Bytecode Instrumentations
  • 職責: 為特定框架提供具體的儀表邏輯,例如對 HttpClient、SQLClient、gRPC 注入觀測碼。
  • 功能: 在 method 內部插入 OTel API(如 StartSpan、SetAttribute)。
OTel Managed Profiler
  • 職責: 在應用執行階段進行進一步的邏輯注入與監控。
  • 用途: 管理細部的 Trace 拓展,例如更細緻的 DB query 或 gRPC 呼叫資訊。
Source Instrumentation / OTel SDK
  • Source Instrumentation: 指開發者手動使用 OTel API(例如 tracer.StartActiveSpan())插入觀測點。
  • OTel SDK: 提供觀測資料處理能力,負責收集、封裝並導出 Trace、Metrics、Logs 至後端。
架構運作流程
  1. 啟動階段: OTel CLR Profiler 掛載至 CLR,監控模組與 method 載入事件。
  2. 注入階段: Bytecode Instrumentations 對 IL 方法注入 StartSpan 等邏輯。
  3. 執行階段: Managed Profiler 進一步補強觀測點(如內部呼叫、Async 方法)。
  4. 資料導出: 所有訊號被 OTel SDK 收集後導出至 Tempo、Jaeger 等觀測平台。

設計重點 : 「模組化、低侵入、高彈性」


三大流程解析
註冊流程
重點:
  1. 設定環境變數:
    • CORECLR_ENABLE_PROFILING=1
    • CORECLR_PROFILER=OTel CLSID
    • CORECLR_PROFILER_PATH=Profiler DLL 路徑
  2. CoreCLR 啟動階段:
    • 檢查是否啟用 Profiling
    • 從 Registry 查找 CLSID 對應 DLL
    • 解析出 Profiler 的實際路徑
  3. 載入與初始化:
    • 載入 Profiler DLL
    • 呼叫 Initialize 方法註冊
    • 透過 ICorProfilerInfo 註冊事件攔截(Set Event Mask)

請求流程
重點 : 系統如何自動蒐集數據 Data (自己說話)
  1. 應用啟動: CoreCLR 發出多種事件通知,包括類別載入、JIT 編譯、方法進入與離開等。
  2. OTel CLR Profiler 攔截: 根據事件動態判斷並透過 IL 注入觀測邏輯(Bytecode Instrumentation)。
  3. Managed Profiler 建立與關閉 Span: 當方法被呼叫(FunctionEnter)時建立 Span;完成(FunctionLeave)後關閉該 Span。
  4. 外部服務呼叫: 如果方法內呼叫外部服務(API、DB),會建立額外的子 Span 表示呼叫鏈。
  5. OTel SDK 負責收集與導出: 所有已關閉的 Span 會統一被 SDK 收集並匯出至後端系統,如 Jaeger 或 Grafana Tempo。
數據流
重點
  • Application: 應用程式啟動,觸發 Auto-Instrumentation。
  • OTel CLR Profiler: 原生層級注入元件,處理 IL 攔截與上下文傳遞。
  • Bytecode Instrumentation: 插入具體的追蹤邏輯至目標方法(如 HTTP 請求)。
  • OTel Managed Profiler: 管理應用邏輯層級的觀測行為,呼叫 Instrumentation Handler。
  • OTel SDK: 中央資料處理站,封裝資料並準備匯出。
  • Collector: 集中資料後導出至 Jaeger(Trace)或 Prometheus(Metrics)。

ASP.NET Core 實作範例
services.AddOpenTelemetry()
    .WithTracing(builder => builder
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddGrpcClientInstrumentation()
        .AddOtlpExporter());

這段程式會自動收集:

  • http.method, http.status_code, http.url
  • trace_id, span_id, user_agent.original
.AddAspNetCoreInstrumentation()
  • Application : 在應用程式層級攔截整體 ASP.NET Core 的 Middleware Pipeline。自動追蹤整個 HTTP 請求從進入到離開的過程。
  • Controller : 對個別 Controller 進行觀測,例如:哪個 endpoint 被呼叫、哪些路由參數被解析
.AddHttpClientInstrumentation()、.AddGrpcClientInstrumentation()
  • 程式對外發出的 HTTP 請求(如 API 呼叫)
  • gRPC 呼叫事件,包含 method 名、service 名、status code
  • 只要你用標準的 HttpClient 或 .NET gRPC,這些都會被攔截、追蹤並補上 TraceContext。
.AddOtlpExporter()
  • 把觀測資料送到你的可視化平台
  • 例如 : OTLP → Grafana Tempo / Honeycomb / Lightstep / Jaeger / New Relic
用 C4 概念則可以劃出 Container 與 Component 內容
Container
Component

小結
Auto-Instrumentation 讓開發者可以不用動原始碼,就輕鬆讓系統「說出程式碼執行的狀態」,對於初學者在導入可觀測性文化中重要的環節,希望這篇介紹 Auto-Instrumentation 對各位有幫助,若以上內容有不清處的地方,歡迎留言討論,Hayyp Coding :)

參考
opentelemetry-dotnet
opentelemetry-dotnet-instrumentation
opentelemetry-dotnet-instrumentation/docs/design.md
dotnet/runtime/blob/main/docs/design/coreclr/botr/profiling

2025年1月16日 星期四

[NET] Task.WhenEach 等待多個任務的更靈活方式

前言
在過去如果要使用多個 Task 在 Dotnet 中可以使用 Task.WaitAll 和 Task.WhenAll 兩個內建的 API,其使用的差異之前也有撰寫文章 [NET] Task 等待多個任務 - Task.WaitAll 與 Task.WhenAll,這兩個方法都會等到所有任務完成後再繼續執行。如果當需求是完成一個就先進行處理,而不是全部完成後該怎麼辦呢 ? 這時就可以使用 Task.WhenEach 方法,來達到你所要的目的,這篇文章就簡單來介紹其概念與如何使用。

介紹
Task.WhenEach 是一種基於 Task.WhenAny 的模式,它的核心理念是當每個 Task 完成時立即執行對應的內容,而不用等待所有 Task 完成後再處理。這在某些場景下能提升應用的回應速度,尤其是當每個任務的執行時間不相同時,避免了某些較早完成的任務閒置等待其他長時間運行的任務,拖累到整體任務的完成時間。讓我們用一個範例來展示 Task.WhenEach 的概念。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

public class Program
{
    static async Task Main(string[] args)
    {
        List> tasks = new List>
        {
            SimulateWork(1, 1000),
            SimulateWork(2, 2000),
            SimulateWork(3, 3000),
            SimulateWork(4, 4000),
            SimulateWork(5, 5000),
        };

        await TaskWhenEach(tasks);
    }

    static async Task SimulateWork(int id, int delay)
    {
        await Task.Delay(delay);
        Console.WriteLine($"Task {id} completed after {delay / 1000.0:F2} seconds");
        return id;
    }

    static async Task TaskWhenEach(IEnumerable> tasks)
    {
        var taskList = tasks.ToList();
        
        while (taskList.Any())
        {
            var completedTask = await Task.WhenAny(taskList);
            taskList.Remove(completedTask);
            Console.WriteLine($"Processed result: {await completedTask}");
        }
    }
}

  
  
簡單說明 Task.WhenEach 的運作方式
1. 建立多個非同步任務,這些任務模擬不同時間長度的工作。
2. 使用 Task.WhenAny 找到第一個完成的任務,立即處理結果。
3. 從列表中移除已完成的任務,然後繼續等待剩餘的任務。
4. 重複上述步驟直到所有任務完成。
這樣的設計確保了不會有執行緒閒置等待較長的任務結束,而是每當一個任務完成就立即執行處理邏輯,提升了 application 或功能的回應速度。

執行結果
Task 1 completed after 1.00 seconds
Processed result: 1
Task 2 completed after 2.00 seconds
Processed result: 2
Task 3 completed after 3.00 seconds
Processed result: 3
Task 4 completed after 4.00 seconds
Processed result: 4
Task 5 completed after 5.00 seconds
Processed result: 5

 

應用場景
即時資料處理:
- 當一組異步任務代表不同的 API 請求時,可以使用 Task.WhenEach 來立即處理已完成的請求,提升使用者體驗。
非同步串流處理:
- 當我們要處理大量非同步工作時,Task.WhenEach 可以確保已完成的工作不會閒置,而是立即傳遞到下一個處理步驟。
UI 更新:
- 在 UI 應用中,當我們需要執行多個背景任務,並希望它們完成後立即更新 UI,可以使用 Task.WhenEach 來確保 UI 變更即時發生。

小結
希望這篇文章能幫助讀者了解 Task.WhenEach 的基本概念與應用,如果有任何問題或建議,歡迎交流討論,happy Coding !

參考
Task.WhenEach 方法

Copyright © m@rcus 學習筆記 | Powered by Blogger

Design by Anders Noren | Blogger Theme by NewBloggerThemes.com