In the DisposeAsync
documentation, you have the following:
class ExampleConjunctiveDisposableusing : IDisposable, IAsyncDisposable
{
...
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
public async ValueTask DisposeAsync()
{
await DisposeAsyncCore().ConfigureAwait(false);
Dispose(disposing: false);
GC.SuppressFinalize(this);
}
Specifically, they call Dispose(false)
in DisposeAsync
.
As I was missing that, I added it to the actual code.
I then started getting random 0xC0000374
(STATUS_HEAP_CORRUPTION).
These would occur as the class frees the pointer a second time.
I tried adding numerous if (Disposed) return;
guards but that didn't fix.
If I remove it the problem is gone... but something seems to be missing now.
Obviously, I use await using
, it's for making an overlapped DeviceIoControl
as a Task
.
Example:
using System.Runtime.InteropServices;
namespace Whatever.Extensions;
public sealed class NativeMemory : IDisposable, IAsyncDisposable
{
private readonly int Length;
private readonly nint Pointer;
private bool Disposed;
public NativeMemory(int length)
{
Pointer = Marshal.AllocHGlobal(Length = length);
}
public Span<byte> Span
{
get
{
unsafe
{
return new Span<byte>((void*)Pointer, Length);
}
}
}
public async ValueTask DisposeAsync()
{
await DisposeAsyncCore().ConfigureAwait(false);
Dispose(false); // BUG when added
GC.SuppressFinalize(this);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private async ValueTask DisposeAsyncCore()
{
Marshal.FreeHGlobal(Pointer);
await ValueTask.CompletedTask;
}
~NativeMemory()
{
Dispose(false);
}
private void Dispose(bool disposing)
{
if (Disposed)
{
return;
}
Marshal.FreeHGlobal(Pointer);
if (disposing)
{
// NOP
}
Disposed = true;
}
}
Question:
How wrong is my implementation?
When problematic line is uncommented, you call Marshal.FreeHGlobal(Pointer)
twice:
public async ValueTask DisposeAsync()
{
// 1st call of Marshal.FreeHGlobal
await DisposeAsyncCore().ConfigureAwait(false);
// 2nd call of Marshal.FreeHGlobal
Dispose(false); // BUG when added
GC.SuppressFinalize(this);
}
Assuming that disposal is fast and we actually don't want async version, but interface implementation only,
why don't we call Dispose(true)
within DisposeAsync()
?
// let's not create any state machine with async + await:
// we free resources synchronously and return completed task
public ValueTask DisposeAsync()
{
// Dispose instance properly
Dispose(true);
// Suppress finalization
GC.SuppressFinalize(this);
// return completed task
return ValueTask.CompletedTask;
}
Please note, that since your class is sealed
you don't have to implement full IDisposable
pattern (there's no use of Dispose(bool disposing)
since the class will never be inherited):
public NativeMemory(int length) {
ArgumentOutOfRangeException.ThrowIfNegative(length);
Pointer = Marshal.AllocHGlobal(Length = length);
// Let GC know, that we allocated some memory
GC.AddMemoryPressure(Length);
}
public void Dispose() {
if (Disposed)
return;
Marshal.FreeHGlobal(Pointer);
// We release memory allocated, let GC know about it
GC.RemoveMemoryPressure(Length);
GC.SuppressFinalize(this);
Disposed = true;
}
~NativeMemory() {
Dispose();
}
public ValueTask DisposeAsync() {
// Dispose instance properly
Dispose();
// return completed task
return ValueTask.CompletedTask;
}