配置Await(false)和IAsyncDisposable的结构体实现

发布于 2025-01-19 03:49:02 字数 1387 浏览 3 评论 0原文

我已经使用 ActionOnAsyncDispose 结构实现了 IAsyncDisposable,如下所示。我的理解是,当它处于 async using 语句中时,编译器不会将其装箱:

ActionOnDisposeAsync x = ...;
await using (x) {
     ...
}

正确吗?到目前为止,一切都很好。我的问题是,当我像这样配置等待时:

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();
        }
    }

    ...
}

I have implemented IAsyncDisposable with an ActionOnAsyncDispose struct as shown below. My understanding is that the compiler will not box it when it is in an async using statement:

ActionOnDisposeAsync x = ...;
await using (x) {
     ...
}

Correct? So far so good. My question is this, when I configure await on it like so:

ActionOnDisposeAsync x = ...;
await using (x.ConfigureAwait()) {
     ...
}

will x be boxed? What about if I put the ConfigureAwait in a method, Caf():

[MethodImpl(MethodImplOptions.AggressiveInlining)]
static public ConfiguredAsyncDisposable Caf(this ActionOnDisposeAsync disposable)
    => disposable.ConfigureAwait(false);

ActionOnDisposeAsync x = ...;
await using (x.Caf()) {
     ...
}

Can I avoid boxing in that case? I was not able to find documentation on what exactly my using variable needs to implement in order to have the effect of ConfigureAwait. There doesn't seem to be any public way of constructing a ConfiguredAsyncDisposable either.

Here is 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();
        }
    }

    ...
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

静赏你的温柔 2025-01-26 03:49:02

是的,ConfigureAwaitstruct 上一次性使用会导致装箱。以下是此行为的实验演示:

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;
}

输出:

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);
}

现在运行与之前相同的实验,无需对代码进行任何更改,也不会导致分配。输出为:

Allocated: 0 bytes per 'await using'

现场演示

Yes, the ConfigureAwait on struct disposables causes boxing. Here is an experimental demonstration of this behavior:

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(
quot;Allocated: {(mem1 - mem0) / loops:#,0} bytes per 'await using'");

...where MyDisposableStruct is this simple struct:

readonly struct MyDisposableStruct : IAsyncDisposable
{
    public ValueTask DisposeAsync() => default;
}

Output:

Allocated: 24 bytes per 'await using'

Live demo.

To prevent the boxing from happening you will have to create a custom ConfiguredAsyncDisposable-like struct, that is tailored specifically for your struct. Here is how it can be done:

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);
}

Now running the same experiment as before, without making any change in the code whatsoever, does not cause allocations. The output is:

Allocated: 0 bytes per 'await using'

Live demo.

吃颗糖壮壮胆 2025-01-26 03:49:02

如果编译器能够检测实际类型(您的结构),则不需要拳击。如果仅通过界面工作,则在处置时会进行。我的检查您的编译代码是否使用ILSPY之类的内容,您将查看是否在类(也是接口的情况)或值类型(/struct)上完成的Distose语句。

我不确定使用结构是否会在处置异步时会获得很多收益,并且是否值得付出努力,但是在决定之前,您应该衡量它。

If the compiler is able to detect the actual type (your struct) it does not require boxing. If it only works via the interface, it will when disposing. My checking your compiled code with something like ILSpy you will see if the dispose statement is done on a class (also the case for interfaces), or on a value type (/struct).

I'm not sure if using a struct will gain you much when disposing async, and if it is worth the effort, but you should measure that before deciding.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文