[英]ConfigureAwait(false) and struct implementation of IAsyncDisposable
我已經使用 ActionOnAsyncDispose 結構實現了 IAsyncDisposable,如下所示。 我的理解是,當它處於異步 using 語句中時,編譯器不會將其裝箱:
ActionOnDisposeAsync x = ...;
await using (x) {
...
}
正確的? 到目前為止,一切都很好。 我的問題是,當我像這樣配置 await 時:
ActionOnDisposeAsync x = ...;
await using (x.ConfigureAwait()) {
...
}
x會被裝箱嗎? 如果我將 ConfigureAwait 放在方法 Caf() 中會怎么樣:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static public ConfiguredAsyncDisposable Caf(this ActionOnDisposeAsync disposable)
=> disposable.ConfigureAwait(false);
ActionOnDisposeAsync x = ...;
await using (x.Caf()) {
...
}
在那種情況下我可以避免拳擊嗎? 我無法找到關於我的 using 變量究竟需要實現什么才能獲得 ConfigureAwait 效果的文檔。 似乎也沒有任何構建 ConfiguredAsyncDisposable 的公共方式。
這是 ActionOnDisposeAsync:
public readonly struct ActionOnDisposeAsync : IAsyncDisposable, IEquatable<ActionOnDisposeAsync>
{
public ActionOnDisposeAsync(Func<Task> actionAsync)
{
this.ActionAsync = actionAsync;
}
public ActionOnDisposeAsync( Action actionSync)
{
this.ActionAsync = () => { actionSync(); return Task.CompletedTask; };
}
private Func<Task> ActionAsync { get; }
public async ValueTask DisposeAsync()
{
if (this.ActionAsync != null) {
await this.ActionAsync();
}
}
...
}
是的, struct
disposables 上的ConfigureAwait
會導致裝箱。 這是此行為的實驗演示:
MyDisposableStruct value = new();
const int loops = 1000;
var mem0 = GC.GetTotalAllocatedBytes(true);
for (int i = 0; i < loops; i++)
{
await using (value.ConfigureAwait(false)) { }
}
var mem1 = GC.GetTotalAllocatedBytes(true);
Console.WriteLine($"Allocated: {(mem1 - mem0) / loops:#,0} bytes per 'await using'");
...其中MyDisposableStruct
是這個簡單的結構:
readonly struct MyDisposableStruct : IAsyncDisposable
{
public ValueTask DisposeAsync() => default;
}
Output:
Allocated: 24 bytes per 'await using'
現場演示。
為了防止裝箱發生,您必須創建一個自定義的ConfiguredAsyncDisposable
類結構,該結構專門為您的結構量身定制。 這是如何完成的:
readonly struct MyConfiguredAsyncDisposable
{
private readonly MyDisposableStruct _parent;
private readonly bool _continueOnCapturedContext;
public MyConfiguredAsyncDisposable(MyDisposableStruct parent,
bool continueOnCapturedContext)
{
_parent = parent;
_continueOnCapturedContext = continueOnCapturedContext;
}
public ConfiguredValueTaskAwaitable DisposeAsync()
=> _parent.DisposeAsync().ConfigureAwait(_continueOnCapturedContext);
}
static MyConfiguredAsyncDisposable ConfigureAwait(
this MyDisposableStruct source, bool continueOnCapturedContext)
{
return new MyConfiguredAsyncDisposable(source, continueOnCapturedContext);
}
現在運行與以前相同的實驗,不對代碼進行任何更改,不會導致分配。 output 是:
Allocated: 0 bytes per 'await using'
現場演示。
如果編譯器能夠檢測到實際類型(您的結構),則不需要裝箱。 如果它僅通過接口工作,它將在處理時使用。 我用 ILSpy 之類的東西檢查你的編譯代碼,你會看到 dispose 語句是在 class 上完成的(對於接口也是如此),還是在值類型 (/struct) 上完成。
我不確定在處理異步時使用結構是否會給你帶來很多好處,以及是否值得付出努力,但你應該在決定之前衡量一下。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.