Blazor Server 中忘記 @implements IAsyncDisposable 帶來的隱性災難
Copyright Notice: This article is an original work licensed under the CC 4.0 BY-NC-ND license.
If you wish to repost this article, please include the original source link and this copyright notice.
Source link: https://v2know.com/article/1136
有時候,一個小細節可能讓你吃足苦頭 —— 特別是當你在 Blazor Server 中使用
DbContext
等需釋放資源的物件時。
🧠 背景:Blazor Server + DbContext 的常見寫法
在 Blazor Server 專案中,我們經常透過依賴注入使用 IDbContextFactory<T>
建立 DbContext
:
@inject IDbContextFactory<ApplicationDbContext> DbFactory
@code {
private ApplicationDbContext context = default!;
protected override async Task OnInitializedAsync()
{
context = await DbFactory.CreateDbContextAsync();
// ...
}
public async ValueTask DisposeAsync()
{
await context.DisposeAsync();
}
}
看起來一切都很完美,對吧?但其實……根本沒有任何資源被釋放!
🚨 問題:DisposeAsync()
永遠不會被呼叫?
是的。
如果你沒有在 Razor 頁面最上方加上這一行:
@implements IAsyncDisposable
那麼 Blazor 根本不會識別你有實作 DisposeAsync()
,就算你寫了也完全不會執行。
原因在於 Blazor 的元件銷毀邏輯:
Blazor 在處理元件生命周期時,會這樣檢查是否應釋放資源:
if (component is IAsyncDisposable asyncDisposable)
await asyncDisposable.DisposeAsync();
只有「真正」實作了介面的元件(透過 @implements
宣告)才會符合 is IAsyncDisposable
,否則一律跳過。
🔬 實際後果:DbContext 泄漏、連線耗盡、記憶體飆高
如果你像我一樣,在多個頁面中都動態建立了 DbContext
,又沒有正確釋放,最終你會看到以下現象:
-
PostgreSQL / MySQL 連線數暴增不回收
-
Memory usage 持續飆升
-
EF Core 查詢變慢甚至 timeout
-
無法釋放的 Socket 資源導致應用程式崩潰
而罪魁禍首竟然只是你少寫了一行 @implements IAsyncDisposable
!
✅ 解法:加上這一行,立即見效
在你的 .razor
檔案最上方加入:
@implements IAsyncDisposable
這樣 Blazor 才會正確在元件銷毀時呼叫你定義的 DisposeAsync()
方法。
📦 範例修正前後對比
❌ 錯誤寫法(未釋放資源):
@page "/example"
@inject IDbContextFactory<AppDbContext> DbFactory
@code {
private AppDbContext context = default!;
protected override async Task OnInitializedAsync()
{
context = await DbFactory.CreateDbContextAsync();
}
public async ValueTask DisposeAsync()
{
await context.DisposeAsync(); // 永遠不會被呼叫
}
}
✅ 正確寫法(Blazor 可識別並釋放資源):
@page "/example"
@implements IAsyncDisposable
@inject IDbContextFactory<AppDbContext> DbFactory
@code {
private AppDbContext context = default!;
protected override async Task OnInitializedAsync()
{
context = await DbFactory.CreateDbContextAsync();
}
public async ValueTask DisposeAsync()
{
await context.DisposeAsync(); // 現在會被自動呼叫
}
}
🧩 補充:IDisposable
vs IAsyncDisposable
如果你用的是同步釋放方法:
public void Dispose()
{
context.Dispose();
}
那麼你應改寫為:
@implements IDisposable
但對於 EF Core 的 DbContext
,建議使用 DisposeAsync()
(即 IAsyncDisposable
),以支援 async 背景下的連線釋放。
🧼 建議習慣:凡是用 DbContext
,就 @implements IAsyncDisposable
無論是:
-
分類頁面
-
歸檔頁面
-
首頁
-
詳情頁
-
搜索結果頁
只要有手動建立 DbContext,就應該記得加上 @implements IAsyncDisposable
。
否則你遲早會在生產環境中因連線、記憶體泄漏等問題付出代價。
📌 總結
問題 | 解法 |
---|---|
DisposeAsync() 沒有生效? | 加上 @implements IAsyncDisposable |
DbContext 沒有釋放? | 用 IDbContextFactory 並在元件釋放時處理 |
多頁面都需要釋放資源? | 每頁都應補上 @implements 宣告 |
沒補會怎樣? | 資源不回收、記憶體爆漲、連線用盡 |
🔚 結語
這是一個 Blazor Server 特有的坑 —— 小到你可能忽略它,大到會讓整個站台掛掉。
如果你也中招了,希望這篇文章能幫你止血。
This article was last edited at