在非托管异步操作期间保持对象存活

时间:2013-07-17 19:27:25

标签: c# garbage-collection .net-4.5 unmanaged

我正在使用一个非托管函数,它接受指向某些非托管内存的指针。该函数在被调用时立即返回,并在后台对内存进行异步操作。它需要一个额外的IntPtr,它在操作完成时传递回我的托管代码。可以同时运行多个此类操作。

我将指向非托管内存的指针封装在自定义SafeBuffer实例中,我希望在异步操作完成时返回该实例。 SafeBuffer确保在没有引用时正确释放内存。问题是当然,在非托管函数仍然使用内存时,不应释放内存。

我怎样才能做到这一点?非托管函数被称为数十亿次,因此性能至关重要。

每当我调用该函数时,我都可以分配一个GCHandle,在操作完成时使用它来获取SafeBuffer,并释放它。但是,分配句柄似乎很昂贵,而且性能会随着时间的推移而显着下降。

我可以分配一次GCHandle,但是当非托管函数没有使用内存并且没有对SafeBuffer的引用时,非托管内存不会被释放。

有什么想法吗?

2 个答案:

答案 0 :(得分:0)

您可以在管理方保留查找表:

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

在调用非托管代码之前调用GetNextHandle。完成后,从handles字典中删除句柄。

句柄只是一个任意long,它是稳定的(不会改变)并且可以传递给非托管代码。

答案 1 :(得分:0)

计算机科学中的所有问题都可以通过另一层次的间接解决:

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实现。)

用户代码创建并保存对SafeMemory实例的引用。 SafeMemory实例和GCHandle保存对1元素SafeMemory数组的引用。当用户代码不再保存对SafeMemory实例的引用时,将释放GCHandle。

在进行非托管异步操作时,会使该数组保存对SafeMemory实例的引用。这意味着GCHandle拥有对数组的引用,并且该数组包含对SafeMemory实例的引用,即使用户代码停止持有对SafeMemory实例的引用,也会阻止GCHandle被释放。