實作 DisposeAsync 方法
介面 System.IAsyncDisposable 是 C# 8.0 的一部分引進。 IAsyncDisposable.DisposeAsync()當您需要執行資源清除時實作 方法,就像實作 Dispose 方法一樣。 不過,其中一個主要差異在於,此實作允許非同步清除作業。 會 DisposeAsync() 傳回 , ValueTask 表示非同步處置作業。
實作 IAsyncDisposable 類別也會實作介面時,通常會是 IDisposable 一般情況。 介面的良好實作 IAsyncDisposable 模式是準備進行同步或非同步處置,不過這不是必要條件。 如果無法同步處置您的類別,則只能 IAsyncDisposable 接受擁有 。 實作處置模式的所有指導方針也適用于非同步實作。 本文假設您已經熟悉如何 實作 Dispose 方法。
警告
如果您實作 IAsyncDisposable 介面,但不是 IDisposable 介面,您的應用程式可能會流失資源。 如果類別實作 ,但不是 IDisposable ,而且取用 IAsyncDisposable 者只會呼叫 Dispose
,則您的實作永遠不會呼叫 DisposeAsync
。 這會導致資源流失。
提示
關於相依性插入,在 中 IServiceCollection 註冊服務時,會代表您隱含管理 服務存留期 。 IServiceProvider和對應的 IHost 協調資源清除。 具體而言,和 IAsyncDisposable 的 IDisposable 實作會在指定的存留期結束時正確處置。
如需詳細資訊,請參閱 .NET 中的相依性插入。
探索 DisposeAsync
和 DisposeAsyncCore
方法
介面 IAsyncDisposable 會宣告單一無參數方法 DisposeAsync() 。 任何非密封類別都應該有另 DisposeAsyncCore()
一個也會傳 ValueTask 回 的方法。
public
IAsyncDisposable.DisposeAsync() 沒有參數的實作。簽
protected virtual ValueTask DisposeAsyncCore()
章為的方法:protected virtual ValueTask DisposeAsyncCore() { }
DisposeAsync
方法
public
無 DisposeAsync()
參數方法會在 語句中 await using
隱含呼叫,其用途是釋放 Unmanaged 資源、執行一般清除,以及指出如果有完成項,則不需要執行。 釋放與 Managed 物件相關聯的記憶體一律是 垃圾收集行程的網域。 因此,它擁有標準實作:
public async ValueTask DisposeAsync()
{
// Perform async cleanup.
await DisposeAsyncCore();
// Dispose of unmanaged resources.
Dispose(false);
// Suppress finalization.
GC.SuppressFinalize(this);
}
注意
與處置模式相比,非同步處置模式的主要差異之一,就是從 DisposeAsync() 多 Dispose(bool)
載方法呼叫會指定 false
為引數。 不過,實作 IDisposable.Dispose() 方法時會 true
改為傳遞。 這有助於確保與同步處置模式的功能相等,並進一步確保仍會叫用完成項程式碼路徑。 換句話說, DisposeAsyncCore()
方法會以非同步方式處置 Managed 資源,因此您也不想以同步方式處置它們。 因此,呼叫 Dispose(false)
而不是 Dispose(true)
。
DisposeAsyncCore
方法
方法 DisposeAsyncCore()
旨在執行 Managed 資源的非同步清除,或對 的串聯呼叫 DisposeAsync()
。 當子類別繼承實作 的 IAsyncDisposable 基類時,它會封裝常見的非同步清除作業。 方法 DisposeAsyncCore()
virtual
可讓衍生類別在其覆寫中定義額外的清除。
提示
如果 的實作 IAsyncDisposable 是 sealed
, DisposeAsyncCore()
則不需要 方法,而且可以在 方法中 IAsyncDisposable.DisposeAsync() 直接執行非同步清除。
實作非同步處置模式
所有非密封類別都應該視為潛在的基類,因為它們可能會繼承。 如果您針對任何可能的基類實作非同步處置模式,則必須提供 protected virtual ValueTask DisposeAsyncCore()
方法。 下列部分範例使用 NoopAsyncDisposable
定義如下的類別:
public sealed class NoopAsyncDisposable : IAsyncDisposable
{
ValueTask IAsyncDisposable.DisposeAsync() => ValueTask.CompletedTask;
}
以下是非同步處置模式的範例實作,其使用藉由傳回 ValueTask.CompletedTask 實作 的 DisposeAsync
自訂 NoopAsyncDisposable
型別。
public class ExampleAsyncDisposable : IAsyncDisposable
{
private IAsyncDisposable? _example;
public ExampleAsyncDisposable() =>
_example = new NoopAsyncDisposable();
public async ValueTask DisposeAsync()
{
await DisposeAsyncCore().ConfigureAwait(false);
GC.SuppressFinalize(this);
}
protected virtual async ValueTask DisposeAsyncCore()
{
if (_example is not null)
{
await _example.DisposeAsync().ConfigureAwait(false);
}
_example = null;
}
}
在上述範例中:
ExampleAsyncDisposable
是實作 介面的非密封類別 IAsyncDisposable 。- 它包含建
IAsyncDisposable
構函式中初始化的私人欄位_example
。 - 方法
DisposeAsync
會委派給DisposeAsyncCore
方法,並呼叫 GC.SuppressFinalize 以通知垃圾收集行程,完成項不需要執行。 - 它包含呼叫
DisposeAsyncCore()
方法的方法_example.DisposeAsync()
,並將 欄位設定為null
。 - 方法
DisposeAsyncCore()
為virtual
,並在 類別中ExampleAsyncDisposable
覆寫。
密封的替代非同步處置模式
如果您的實作類別可以是 sealed
,您可以覆 IAsyncDisposable.DisposeAsync() 寫 方法來實作非同步處置模式。 下列範例示範如何實作密封類別的非同步處置模式:
public sealed class SealedExampleAsyncDisposable : IAsyncDisposable
{
private readonly IAsyncDisposable _example;
public SealedExampleAsyncDisposable() =>
_example = new NoopAsyncDisposable();
public ValueTask DisposeAsync() => _example.DisposeAsync();
}
在上述範例中:
SealedExampleAsyncDisposable
是實作 介面的 IAsyncDisposable 密封類別。- 包含
_example
的欄位為readonly
,並在建構函式中初始化。 - 方法會
DisposeAsync
_example.DisposeAsync()
呼叫 方法,透過包含的欄位實作模式, (級聯處置) 。
實作 dispose 和 async dispose 模式
您可能需要同時 IDisposable 實作 和 IAsyncDisposable 介面,特別是當您的類別範圍包含這些實作的實例時。 這麼做可確保您可以適當地串聯清除呼叫。 以下是實作兩個介面的範例類別,並示範清理的適當指引。
class ExampleConjunctiveDisposableusing : IDisposable, IAsyncDisposable
{
IDisposable? _disposableResource = new MemoryStream();
IAsyncDisposable? _asyncDisposableResource = new MemoryStream();
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
public async ValueTask DisposeAsync()
{
await DisposeAsyncCore().ConfigureAwait(false);
Dispose(disposing: false);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_disposableResource?.Dispose();
(_asyncDisposableResource as IDisposable)?.Dispose();
_disposableResource = null;
_asyncDisposableResource = null;
}
}
protected virtual async ValueTask DisposeAsyncCore()
{
if (_asyncDisposableResource is not null)
{
await _asyncDisposableResource.DisposeAsync().ConfigureAwait(false);
}
if (_disposableResource is IAsyncDisposable disposable)
{
await disposable.DisposeAsync().ConfigureAwait(false);
}
else
{
_disposableResource?.Dispose();
}
_asyncDisposableResource = null;
_disposableResource = null;
}
}
IDisposable.Dispose()和 IAsyncDisposable.DisposeAsync() 實作都是簡單的重複使用程式碼。
在多載方法中 Dispose(bool)
IDisposable ,如果 實例不是 null
,則會有條件地處置 。 實例 IAsyncDisposable 會轉換成 IDisposable ,如果它也是 null
,也會處置它。 然後,這兩個實例都會指派給 null
。
DisposeAsyncCore()
使用 方法時,會遵循相同的邏輯方法。 IAsyncDisposable如果 實例不是 null
,則會等候其對 DisposeAsync().ConfigureAwait(false)
的呼叫。 IDisposable如果 實例也是 的實作 IAsyncDisposable ,它也會以非同步方式處置。 然後,這兩個實例都會指派給 null
。
使用非同步可處置
若要正確地取用實作 IAsyncDisposable 介面的物件,您可以使用 await並使用關鍵字 。 請考慮下列範例,其中 類別 ExampleAsyncDisposable
會具現化,然後包裝在 語句中 await using
。
class ExampleConfigureAwaitProgram
{
static async Task Main()
{
var exampleAsyncDisposable = new ExampleAsyncDisposable();
await using (exampleAsyncDisposable.ConfigureAwait(false))
{
// Interact with the exampleAsyncDisposable instance.
}
Console.ReadLine();
}
}
重要
ConfigureAwait(IAsyncDisposable, Boolean)使用 介面的 IAsyncDisposable 擴充方法,設定如何在其原始內容或排程器上封送處理工作接續。 如需 的詳細資訊 ConfigureAwait
,請參閱 ConfigureAwait 常見問題。
對於不需要 使用 ConfigureAwait
的情況, await using
可以簡化 語句,如下所示:
class ExampleUsingStatementProgram
{
static async Task Main()
{
await using (var exampleAsyncDisposable = new ExampleAsyncDisposable())
{
// Interact with the exampleAsyncDisposable instance.
}
Console.ReadLine();
}
}
此外,也可以寫入以使用 using 宣告的隱含範圍。
class ExampleUsingDeclarationProgram
{
static async Task Main()
{
await using var exampleAsyncDisposable = new ExampleAsyncDisposable();
// Interact with the exampleAsyncDisposable instance.
Console.ReadLine();
}
}
單行中的多個 await 關鍵字
await
有時候關鍵字可能會在單行內出現多次。 例如,請考慮下列程式碼:
await using var transaction = await context.Database.BeginTransactionAsync(token);
在上述範例中:
- 等候 BeginTransactionAsync 方法。
- 傳回型別為 DbTransaction ,它會實作
IAsyncDisposable
。 - 會
transaction
以非同步方式使用 ,也等候。
堆疊 using
在建立和使用實作 的多個 物件 IAsyncDisposable 的情況下,使用 堆疊 await using
語句 ConfigureAwait 可能會防止在異常狀況中呼叫 DisposeAsync() 。 若要確保 DisposeAsync() 一律呼叫 ,您應該避免堆疊。 下列三個程式碼範例顯示改用的可接受的模式。
可接受的模式 1
class ExampleOneProgram
{
static async Task Main()
{
var objOne = new ExampleAsyncDisposable();
await using (objOne.ConfigureAwait(false))
{
// Interact with the objOne instance.
var objTwo = new ExampleAsyncDisposable();
await using (objTwo.ConfigureAwait(false))
{
// Interact with the objOne and/or objTwo instance(s).
}
}
Console.ReadLine();
}
}
在上述範例中,每個非同步清除作業都會明確限定在 await using
區塊底下。 外部範圍的定義方式是 objOne
設定其大括弧、括住 objTwo
,例如 objTwo
先處置,後面接著 objOne
。 這兩 IAsyncDisposable
個實例都有等候的方法 DisposeAsync() ,因此每個實例都會執行其非同步清除作業。 呼叫是巢狀的,不會堆疊。
可接受的模式 2
class ExampleTwoProgram
{
static async Task Main()
{
var objOne = new ExampleAsyncDisposable();
await using (objOne.ConfigureAwait(false))
{
// Interact with the objOne instance.
}
var objTwo = new ExampleAsyncDisposable();
await using (objTwo.ConfigureAwait(false))
{
// Interact with the objTwo instance.
}
Console.ReadLine();
}
}
在上述範例中,每個非同步清除作業都會明確限定在 await using
區塊底下。 在每個區塊結束時,對應的 IAsyncDisposable
實例會等候其 DisposeAsync() 方法,因此執行其非同步清除作業。 呼叫是循序的,不會堆疊。 在此案例 objOne
中,會先處置,然後 objTwo
處置。
可接受的模式三
class ExampleThreeProgram
{
static async Task Main()
{
var objOne = new ExampleAsyncDisposable();
await using var ignored1 = objOne.ConfigureAwait(false);
var objTwo = new ExampleAsyncDisposable();
await using var ignored2 = objTwo.ConfigureAwait(false);
// Interact with objOne and/or objTwo instance(s).
Console.ReadLine();
}
}
在上述範例中,每個非同步清除作業都會隱含地以包含的方法主體來設定範圍。 在封入區塊結束時, IAsyncDisposable
實例會執行其非同步清除作業。 這會以宣告它們的反向循序執行,這表示 objTwo
在 之前 objOne
處置。
無法接受的模式
下列程式碼中反白顯示的行會顯示「堆疊使用」的意義。 如果從 AnotherAsyncDisposable
建構函式擲回例外狀況,則不會正確處置兩個物件。 objTwo
變數永遠不會指派,因為建構函式未順利完成。 因此,的 AnotherAsyncDisposable
建構函式會負責處置在擲回例外狀況之前配置的任何資源。 ExampleAsyncDisposable
如果類型具有完成項,則其符合最終處理資格。
class DoNotDoThisProgram
{
static async Task Main()
{
var objOne = new ExampleAsyncDisposable();
// Exception thrown on .ctor
var objTwo = new AnotherAsyncDisposable();
await using (objOne.ConfigureAwait(false))
await using (objTwo.ConfigureAwait(false))
{
// Neither object has its DisposeAsync called.
}
Console.ReadLine();
}
}
提示
避免此模式,因為它可能會導致非預期的行為。 如果您使用其中一個可接受的模式,則未散發的物件問題不存在。 當語句未堆疊時 using
,會正確執行清除作業。
另請參閱
如需 和 IAsyncDisposable
的雙重實作 IDisposable
範例,請參閱 Utf8JsonWriterGitHub 上的原始程式碼。