[제목] 작업쓰레드에서 BeginInvoke()를 쓰는 오류
한 개발자가 아래와 같은 코드를 작성하였다. 이 코드는 작업쓰레드에서 실행되었는데, 파일을 복사하면서 ProgressBar를 가진 폼에 복사 진척 정도를 표시하는 일을 하는 것이다.
using (Stream ins = File.Open(sourceFile, FileMode.Open)) { using (Stream outs = File.Create(targetFile)) { while (ins.Position < ins.Length) { outs.WriteByte((byte)ins.ReadByte()); progressBarForm.BeginInvoke( new Action ( () => progressBarForm.UpdateProgress(filename, ins.Position, ins.Length) ) ); } } }
그런데 이 코드가 실행 중에 "Cannot access a closed file." 라는 에러를 발생시켰다. 에러메시지가 암시하듯이, 이는 파일이 Close 된 상태에서 해당 파일을 엑세스하려 하였기 때문에 발생한 것이다.
위 코드는 작업쓰레드에서 실행되었기 때문에, UI 컨트롤(progressBarForm)을 직접 엑세스할 수 없다. 윈도우즈 프로그래밍에서 UI 컨트롤은 해당 컨트롤을 생성한 쓰레드, 그리하여 컨트롤의 윈도우 핸들을 소유(Own)하고 있는 쓰레드(UI Thread)에서만 엑세스할 수 있다.
따라서, 만약 작업쓰레드에서 컨트롤을 엑세스해야 한다면, Control.Invoke(delegate) 혹은 Control.BeginInvoke(delegate) 메서드를 사용해서 해당 컨트롤의 윈도우 핸들을 갖는 UI 쓰레드에서 (파라미터에 지정된) delegate를 실행하게 하여야 한다.
그런데, 위의 코드에서는 BeginInvoke()를 사용하여 progressBarForm 폼을 비동기적으로 갱신하려 하였는데, while 루프에서 처리되는 파일 복사 처리가 빨리 실행되어 급기야는 폼을 갱신하는 일보다 더 빨리 파일을 Close하게 되었다. 따라서 Delegate에 전달하는 ins.Position과 ins.Length를 Stream이 닫힌 상태에서 엑세스할 수 없게 된 것이다.
문제의 해결책은 비교적 간단하다. progressBarForm.BeginInvoke() 대신 progressBarForm.Invoke()를 사용하여 progressBarForm 폼 갱신을 동기적으로 처리하는 것이다. 이렇게 하면 폼이 갱신될 때까지 작업쓰레드가 기다리기 때문에 파일 스트림이 미리 닫힐 염려가 없게 된다.
본 웹사이트는 광고를 포함하고 있습니다. 광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.