How to dispose an unmanaged resource/object that doesn't implement IDisposable

77 Views Asked by At

Actually question is simple but I wanted to provide as much information as possible to help everyone to understand problem/issue. For this reason question may seem long/hard.

My question is briefly: how to dispose/destroy an unmanaged resource/object that doesn't implement IDisposable interface.

I'm getting data from Google Spreadsheet by requesting (executing) query. Sometimes data which is retrieved could be really large (i.e. 1.5-2 GB) and it's being loaded into directly memory. The retrieved data comes as JSON format and automatically deserialized to Spreadsheet object type like in this line

I cannot dispose this object completely from memory since this is an unmanaged resource (doesn't implement IDisposable).

I've taken couple of images of memory consumption during execution (retrieval of data from Google Spreadsheet API). There are three phases;

  • Before execution,
  • Right after execution and during of manipulation the object,
  • After disposing (calling GC to clean up).

For instance if I try to get a Spreadsheet object;

  • In the first phase memory state is 53 MB,
  • In the second phase, after execution completes (just 1 line of code, request.Execute(), loads the object into memory in 1.st block of code.) As shown below, memory state is 1.7 GB.
  • In the third phase, after calling GarbageCollector for clean up, memory state is 405 MB

Memory consumption graph between phases

As it's seen in the above memory graph as well there is a memory leak which is about 350 MB that I cannot free by using traditional ways. In the second phase I'm manipulating this raw data to convert into human readable format to show user on GUI. The data I'm obtaining after manipulation could be only 1-2 MB not more. But 350 MB of useless data is lost somewhere of memory.

So far I have searched;

  • GarbageCollector working principle and disposing implementations

  • IDisposable and tried as it's shown 6.th code block and it cannot dispose the object completely if I use it with a using block. This only can dispose most of it but not all of it as I described it as three phases in above figure.

  • C# pointers (and could't implement for this scenario)

  • SafeHandle and it's derived types. This is a really deep-dive topic and is hard to understand for me.

    • First, I've created a Spreadsheet wrapper for manage this unmanaged resource as I wrote in the 2.nd block of code
    • Second, I've created, and special SafeHandle class in the 3.rd block of code. This inherits from SafeHandleZeroOrMinusOneIsInvalid (implements IDisposable) and calls ReleaseHandle() automatically when _sdSheetSafeHandle.Dispose() is called inside "SpreadSheetResponse" wrapper class.
    • Third (I think this is the core issue/place I'm got stuck), I've tried to import CloseHandle WinAPI and use it in a static class call as NativeMethod. As far as my understanding this releases unmanaged object based on given IntPtr object. But I don't know how to convert/get an object of pointer as IntPtr.
    • Forth, I've tried to get IntPtr (or pointer by '&') by "ToIntPtr" extension that is present in ObjectHandleExtensions from SpreadSheet object to be able to set in 3.rd block of code as SetHandle (responseHandle). But I'm getting some error in 3.rd block code when executing return NativeMethod.CloseHandle(handle) also shown in the below figure.

Error message when disposing object.

So if I summarize the question; How can I dispose this Spreadsheet object to free the memory after I'm done with it. According to my research, the best options is to use SafeHandle method but I couldn't implement it. Can you give me a roadmap to overcome this issue?

1.

private static void ExecuteGetQuery()
{
    SpreadsheetsResource.GetRequest request = _SheetService.Spreadsheets.Get(_GoogleSpreadSheetIdentifier);

    request.IncludeGridData = true;
    // here is first phase, before execution
    using (SpreadSheetResponse response = new SpreadSheetResponse(request.Execute()))
    {
        //Console.Write(response.GoogleSpreadSheet.Properties.Title);
        // Here is second phase, after getting data from Google Sheet and manipulating data to convert human-readable form to show user on GUI.
    }
    // Here is third phase, which is response object must be properly disposed to free memory. 
}
public class SpreadSheetResponse : IDisposable
{
    private SpreadSheetSafeHandle _sdSheetSafeHandle;
    private Spreadsheet? _sdSheet;
    public Spreadsheet? GoogleSpreadSheet => _sdSheet;
    public SpreadSheetResponse(Spreadsheet response)
    {
        _sdSheet = response;
        _sdSheetSafeHandle = new SpreadSheetSafeHandle(response.ToIntPtr());
    }

    public void Dispose()
    {
        if (_sdSheetSafeHandle != null && !_sdSheetSafeHandle.IsInvalid)
        {
            // Free the handle
            _sdSheetSafeHandle.Dispose();
        }
    }
}
internal class SpreadSheetSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    public SpreadSheetSafeHandle(IntPtr responseHandle) : base(true)
    {
        SetHandle(responseHandle);
    }

    protected override bool ReleaseHandle()
    {
        return NativeMethod.CloseHandle(handle);
    }
}
internal static class NativeMethod
{
    [DllImport("kernel32", SetLastError = true)]
    //[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
    internal extern static bool CloseHandle(IntPtr handle);

    //[System.CLSCompliant(false)]
    //public static explicit operator IntPtr(void* value);
}
public static class ObjectHandleExtensions
{
    public static IntPtr ToIntPtr(this Spreadsheet target)
    {
        unsafe
        {
            //Spreadsheet* sheet_pointer = ⌖
            return (IntPtr)(&target);
        }
    }
}
public class SpreadSheetResponse : IDisposable
{
    public readonly SpreadSheetResponseType ResponseType;
    public Spreadsheet? SdSheet => _sdSheet;
    private Spreadsheet? _sdSheet;
    public SpreadSheetResponse(SpreadSheetResponseType type, Spreadsheet response)
    {
        ResponseType = type;
        this._sdSheet= response;
    }

    public void Dispose()
    {
        this._sdSheet = null;
        GC.Collect(3, GCCollectionMode.Forced, true, true);
    }
}
0

There are 0 best solutions below