C# 리소스(관리 또는 비관리) 사용에 대한 문제점과 해결 방법

C# 리소스(관리 또는 비관리) 사용에 대한 문제점과 해결 방법



C#은 CLR에 의해 모든 것이 관리된다. 그로 인해서 메모리 해제 실수를 하지 않게 된다. 근래 C# 프로그램을 작성하면서 굉장히 많은 이득을 얻을 수 있었는데 그 중 하나가 메모리 관리에 대한 부담이 사라진다는 점이다.

하지만, 관리되지 않는 리소스를 사용할 경우에 메모리 릭이나 프로그램이 멎어 버리는 현상이 발생하기 때문이다. 이러한 프로그램들이 문제인 것은 프로그램을 실행시켜보고 바로 다운되지는 않고, 메모리가 서서히 증가 되면서 죽거나, 멈추게 된다.


문제해결 방법 정리


- 관리되지 않는 리소스의 정의
- 리소스 유출 사실확인 방법
- 해결방법


관리되지 않는 리소스.


MS의 문서에 의하면 관리되지 않는 리소스는 리소스를 래핑 하는 객체가 소멸될 때 그 소멸자를 명시적으로 호출하지 않을 때 리소스가 해제 되지 않는다고 한다. 즉 A라는 본래 리소스가 있고 B라는 리소스 래핑 객체가 생성되어 A를 사용하고 있다. 내부적으로 A를 가지고 있을 것이며 이것이 소멸될 때 A를 참조하지 않아야 한다. 그렇게 해야만 최종적으로 프로그램이 종료될 때 A라는 객체가 소멸된다. 래핑 객체가 B, C, D로 많아 질 경우에 이러한 래핑 객체는 가비지 컬렉터에 의해서 소멸되지만 그 내부에서 참조하는 것은 소멸되지 않는다. 명시적으로 소멸자를 호출해 주어야 한다. (내부적으로 래핑 객체가 어떻게 구성되었을지 모른다면.)


리소스 유출 사실 확인 방법


Windows Task Manager를 이용하면 된다.
 


열 선택을 하여 우리가 보고 싶은 정보를 선택한다.



그리고 나서 작업 관리자를 보면 GDI객체나 핸들 수를 알아낼 수 있다.

private void Form1_MouseMove(object sender, MouseEventArgs e)
{
    for (int i = 0; i < 10; i++)
    {
        Bitmap bmp = new Bitmap(100, 100);
        Graphics g = Graphics.FromImage(bmp);

        g.DrawImage(MouseImg, 50, 50);

        Cursor newCursor = new Cursor(bmp.GetHicon());

        this.Cursor = newCursor;

        g.Dispose();
    }
}

위의 코드는 비트맵 마우스 커서를 설정하는 것이고 MouseMove가 움직일 때 설정하게 한 코드이다.

이 코드를 실행하고 작업 관리자의 핸들 수를 보면 MouseMove가 실행될 때 핸들 수가 증가하지만 MouseMove가 끝났음에도 불구하고 핸들은 줄어들지 않는다. 즉 메모리가 유실되고 있는 것이다.

해결 방법


MS의 문서에 이미 설명이 되어 있지만 너무 간단하게만 다루고 있다. 관리되지 않는 리소스는 바로 저런 핸들이다.

private void Form1_MouseMove(object sender, MouseEventArgs e)
{
    for (int i = 0; i < 5; i++)
    {
        Bitmap bmp = new Bitmap(50, 50);
        Graphics g = Graphics.FromImage(bmp);

        g.DrawImage(MouseImg, 50, 50);

        // 핸들로 객체를 만들어서 할당한 후에
        IntPtr refObj = bmp.GetHicon();
        Cursor newCursor = new Cursor(refObj);
        this.Cursor = newCursor;

        // 핸들을 삭제해 주어야 한다.
        DestroyIcon(newCursor.Handle);
        g.Dispose();
    }
}

물론 DestroyIcon함수를 사용하기 위해서 다음과 같이 코드를 작성해 주어야 한다.

// 아래 2개를 using해주어야 한다.

using Microsoft.Win32;
using System.Runtime.InteropServices;
[System.Runtime.InteropServices.DllImport(“user32.dll”, CharSet = CharSet.Auto)]

extern static bool DestroyIcon(IntPtr handle);

프로그램을 실행하여 정상 동작하는 프로그램 화면.



댓글