Question

I'm working with an unmanaged function that takes a pointer to some unmanaged memory. The function returns immediately when called and asynchronously operates on the memory in the background. It takes an additional IntPtr that it passes back to my managed code when the operation completes. It's possible to have multiple such operations running at the same time.

I'm encapsulating the pointer to unmanaged memory in a custom SafeBuffer instance that I'd like to get back when an asynchronous operation completes. The SafeBuffer ensures that the memory gets properly released when there are no references to it. The problem is that the memory, of course, shouldn't be released while it's still in use by the unmanaged function.

How can I achieve this? The unmanaged function is called billions of times, so performance is critical.

I could allocate a GCHandle whenever I call the function, use it to get the SafeBuffer back when the operation completes, and free it. However, allocating handles seems to be expensive and performance decreases significantly over time.

I could allocate a GCHandle once, but then the unmanaged memory does not get released when the memory is not in use by the unmanaged function and there are no references to the SafeBuffer.

Any ideas?

Était-ce utile?

La solution 2

All problems in computer science can be solved by another level of indirection:

public class SafeMemory : SafeBuffer
{
    private GCHandle referenceHandle;
    private SafeMemory[] reference;

    public SafeMemory()
    {
        this.reference = new SafeMemory[1];
        this.referenceHandle = GCHandle.Alloc(this.reference);
    }

    ~SafeMemory()
    {
        this.referenceHandle.Free();
    }

    public void Start()
    {
        this.reference[0] = this;

        StartUnmanagedAsyncOperation(this, this.referenceHandle);
    }

    static void AsyncOperationCompleted(GCHandle referenceHandle)
    {
        ((SafeMemory[])referenceHandle.Target)[0] = null;
    }
}

(SafeBuffer implementation omitted for brevity.)

The user code creates and holds a reference to a SafeMemory instance. The SafeMemory instance and a GCHandle hold a reference to an 1-element SafeMemory array. The GCHandle is released when the user code no longer holds the reference to the SafeMemory instance.

While an unmanaged asynchronous operation is in progress, the array is made to hold a reference to the SafeMemory instance. This means the GCHandle holds a reference to the array and the array holds a reference to the SafeMemory instance, which prevents the GCHandle from being released even if the user code stops holding the reference to the SafeMemory instance.

Autres conseils

You could keep a lookup-table on the managed side:

static ConcurrentDictionary<long, SafeBuffer> handles = new ...();
static long nextHandle = 0;

static long GetNextHandle(SafeBuffer buffer) {
    var handle = Interlocked.Increment(ref nextHandle);
    handles.Add(handle, buffer);
    return handle;
}

You call GetNextHandle before calling into unmanaged code. On completion you remove the handle from the handles dictionary.

A handle is just an arbitrary long that is stable (does not change) and can be passed to unmanaged code.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top