티스토리 뷰

C# 관리되는, 되지 않는 리소스 사용에 대한 문제점과 해결방법

Written by ProgC

2007-05-31

C#은 CLR에 의해 모든 것이 관리된다. 그로 인해서 메모리 해제를 까먹는다거나 하는 실수를 하지 않게 된다. 실제 필자도 C계열 언어를 10년이 넘게 사용해오면서 메모리 관리가 그다지 쉽지 많은 않다는 것을 피부로 느꼈다. 근래 C#으로 프로그램을 작성하면서 굉장히 많은 이득을 얻을 수 있었는데 그 중 하나가 메모리 관리에 대한 부담이 사라진다는 점이다.
C#에 마법은 없다.
관리되지 않는 리소스를 사용할 경우에 메모리 릭이나 프로그램이 멎어 버리는 현상이 발생하기 때문이다. 이러한 프로그램들이 문제인 것은 프로그램을 실행시켜보고 바로 다운되지는 않는다는 점이다. 단지 서서히 죽어갈 뿐이다.
문제점은 알았으니 문제를 어떻게 풀어 나가야 할 지 다음과 같이 정리했다.
1. 관리되지 않는 리소스란 무엇인가?
2. 리소스가 유출 된다는 사실을 어떻게 알아낼 수 있는가?
3. 해결방법은 무엇인가?

관리되는 리소스는 이미 많은 책들에서 코드와 함께 다루고 있으므로 여기에서는 제외하기로 한다. 우리의 문제는 관리되지 않는 리소스이므로 이것에 대해서만 집중하기로 한다.

관리되지 않는 리소스.

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

리소스가 유출 된다는 사실을 어떻게 알아낼 수 있는가?

불행하게도 Visual C# 2005에서 메모리가 새는 방법을 알아내는 방법은 없었다. 대신 Windows Task Manager를 이용하면 된다.

Array

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

Array

그리고 나서 작업 관리자를 보면 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 < 10; i++)

{

Bitmap bmp = new Bitmap(100, 100);

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

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

Array

마지막.

원칙.

C#은 C#답게 쓰자. 라는 원칙으로 C# 프로그램을 작성하고 있지만 언제까지 MS믿고 C

버전 올려줄 때까지 기다려주지 못한다. 정말 피할 수 없을 때에만 WinAPI코드를 사용하는 것이 좋다.

핫 스팟.

Cursor클래스에서 이미지를 이용해서 커서를 만들 때 핫스팟은 이미지의 중앙을 뜻한다. 그래서 50, 50으로 위치를 이동시켜준 것이다.

'Application > C#' 카테고리의 다른 글

닷넷에서의 INI 파일 입출력  (0) 2010.07.15
C# Frequently Asked Questions for C++ programmers  (0) 2008.03.18
시간 간격 구하기  (1) 2008.01.28
An Asynchronous Socket Server and Client  (0) 2008.01.28
Using Updater Block  (0) 2008.01.28