이미지 뷰어 작업시 메모리 관리에 대해 질문드립니다.

(휴대폰 작성 및 소스코드가 모두 회사에 있는 관계로 디테일한 코드가 누락되니 양해바랍니다.)

여러 비트맵 이미지를 일반적인 문서 뷰어처럼 스크롤하며 볼 수 있게 만들어야 합니다.

현재, 스크롤 뷰어 안에 아이템스컨트롤을 배치하고 아이템스소스를 모델과 바인딩 한 후, 각각의 컨텐트 프레젠터를 이미지 컨트롤로 만들어 주었습니다.

모델에서는 BitmapImage 클래스를 멤버로 소유하고 있고, 사용자가 스크롤을 이동할 때마다 뷰포트 내부에 드러난 페이지의 이미지를 렌더링한 후, 나머지 페이지의 이미지는 모두 메모리에서 해제하려 합니다.

그러나 BitmapImage 클래스에서는 표준Dispose 패턴을 구현해주고 있지 않으므로 BitmapImage에 null을 대입한 후 BitmapImage가 참조중인 stream을 dispose 시키고 가비지컬렉터를 호출하는 방법을 사용하고 있습니다.

코드로 간략히 표현하면 다음과 같습니다.
(예제임을 감안 부탁드리겠습니다.)

class PictureModel
{
public BitmapImage Source { get; set; }

}

class PictureService
{
private Dictionary _cacheManager; // 인코딩된 이미지 캐싱
private Dictionary<int, Stream> _pictureManager;

public BitmapImage Render(int page)
{
// 디코딩된 BitmapImage 생성
}

public void Release(int page)
{
// 딕셔너리에서 스트림 제거
}
}

class PictureViewModel
{

public OvservableCollection Models;

public void ViewportChanged()
{
// Models를 순회하며 현재 뷰포트 영역에 보이지 않는 리소스 탐지
// Models.Source = null 대입
// GC.Collect() 호출
// 뷰포트에 가시화된 영역은 BitmapImage 생성
}
}

이 방식으로도 그럭저럭 동작은 하고 있습니다만, 아무래도 불만족스러운 것입니다. 우선 강제로 가비지 컬렉터를 동작시켜야 하는 부분이 부담스럽고, 스크롤을 매우 빠르게 움직일 경우에 (뷰포트에 나타나야하는 이미지도 순간적으로 늘어남으로) 메모리를 매우 많이 점유하게 됩니다. 물론 가비지 컬렉터에 요청을 보내놓기 때문에 곧 가비지가 회수되기는 합니다만, 만족할만큼 안정적이지는 않습니다.

이미지 컨트롤의 소스에 바인딩이 가능한 BitmapImage에서는 별도의 메모리 관리 기능을 제공하고 있는 것 같지 않은데, 혹시 wpf에서 비트맵 리소스들을 확실히 관리할 수 있는 더 나은 대안이 있을까요?

3개의 좋아요

xaml을 어떻게 구성하셨는지는 일단 모르는 상태에서 말씀드리면, 용어는 model과 viewmodel 이라는 용어를 쓰셨지만 model에서 bitmapimage라는 wpf fcl 클래스를 쓰신 상태면 mvvm을 지향하시는 것은 아니라고 생각이 됩니다. mvvm이라면…물론 아키텍쳐 구성은 봐야겠지만 model은 보통 bcl에는 의존적으로 작성할 수 있지만, fcl에는 비의존적으로 개발하는 것이 일반적인 mvvm이기 때문입니다.

mvvm이라면 model에서는 image를 들고 있는 상태에서 converter에서 stream으로 그때그때 image를 그려주고 converter 안에서 using을 사용하면 gc.collect를 호출할 필요도 없습니다. 아시겠지만 stream이 제거가 되면 그와 바인딩된 객체 역시 함께 제거되기 때문에 image도 함께 제거되기 때문입니다.

물론 xaml의 구성과 전체적인 그림을 봤을 때 실제 적용하면 어떨지는 해봐야 알고 더 자세한 정보를 알아야 가능하겠지만, 위 방식대로 하는 것은 wpf에서 무척 일반적인 방식이긴 합니다.

gc.collect를 직줍 호출 하는 것도 사실 .net에서는 지양기도 하고…

3개의 좋아요

이미지 컨트롤의 소스로 바인딩할 수 있는 속성이 ImageSource를 상속받은 클래스 뿐이라 해당 클래스를 통한 바인딩이 자연스러운 것인 줄 알았네요! BitmapImage의 메모리 관리에 대해서 gc를 호출하라는 의견도 워낙 많이 검색되어 이용 방법에 대해 오해가 있었습니다. 저도 이것이 워낙에 이상하여 질문드리게 됐습니다. 그러면 모델에서는 스트림만 보관하고, 컨버터를 통해 이미지 컨트롤에 바인딩 하는 방법을 주로 사용하게 되나요?

2개의 좋아요

넵 이해하신 것이 일반적인 방법이 맞습니다.

정확히는 model 클래스에서는 image, bitmap 클래스를 갖게 되고

IValueConverter를 구현하는 Converter 클래스 안에서 바인딩되는 input 데이터를 image로 변경해서 그 image를 memorystream으로 using을 사용해서 열어서 쓰시면 됩니다.

아마 chatgpt나 stackoverflow를 찾아보시면 image 클래스를 imagesource나 bitmapimage로 변경해주는 converter를 많이 찾아보실 수 있으실 것 같습니다.

4개의 좋아요

답변 감사합니다! 크게 도움이 됐습니다.

2개의 좋아요

아, 그리고 만약에 converter를 이용한 동작이 의도대로 동작하지 않으신다면, converter는 converter대로 쓰시고 stream도 stream대로 쓰시고 다만 using 으로 여시는건 아니고 제거를 원하시는 시점에 image를 dispose 시키시는 방법도 있습니다.

image를 list같은데 저장해서 쓰시다가 foreach로 순회하면서 dispose를 하면 될것입니다.

이렇게 하면 gc.collect를 호출하지 않고도 image와 연결된 stream과 같은 중간 layer의 객체들을 제거할 수 있을 것입니다.

4개의 좋아요

감사합니다!

2개의 좋아요