[Delphi] Application.ProcessMessages 사용시 주의점

Clover·2022년 3월 12일
0

Delphi

목록 보기
2/12
post-thumbnail

Application.ProcessMessages

델파이 어플리케이션은 "싱글 쓰레드"로 동작한다.
그래서 시간이 오래걸리는 동작을 할 때, 사용자가 마우스로 화면을 클릭하는 등의 다른 이벤트를 발생시키면 프로그램이 응답없음 상태가 된다.

위와 같은 문제를 해결하기 위해 Application.ProcessMessages; 프로시져를 사용하곤 하는데, 이 프로시져를 사용할 때 주의해야 할 점에 대한 글을 보고 정리를 해보았다.

참고한 글:


주의점

Application.ProcessMessages;는 Button Click event 혹은 Window Movement와 같은 모든 대기 메시지를 핸들링(처리)해주는 Procedure 이다.

예시로, DB에 데이터를 만 건 이상 Insert하는 버튼을 만들어서 클릭하면, 데이터가 Insert 되는 시간동안 (그 버튼의 Click Event가 완료될 때 까지) 싱글 쓰레드인 델파이 어플리케이션은 Lock 상태가 된다.

procedure TForm1.Button1Click(Sender: TObject);
var
  i :Integer;
begin
 
  for i := 0 to 10000 do
  begin
    {Insert Into ....}
  end;
 
  {for문이 끝날때까지 Application은 Lock 상태가 된다.}
end;

이 때, 매 반복마다 (혹은 다음단계로 넘어갈 때 마다) Application.ProcessMessages; 를 호출해주면 버튼의 Click Event가 처리되면서 대기열에 또 다른 이벤트를 받을 수 있는 상태가 된다.

procedure TForm1.Button1Click(Sender: TObject);
var
  i :Integer;
begin
 
  for i := 0 to 10000 do
 
  begin
 
    {Insert Into ....}
 
    Application.ProcessMessages; //추가
 
  end;
 
  {for문이 끝날때까지 Application은 Lock 상태가 된다.}
 
end;

(반복문이 동작하는 도중에도 어플리케이션을 드래그해서 위치를 옮기는 등의 행위를 할 수 있다.)


따라서, 내가 만든 프로그램이 "응답없음" 상태가 되는것을 방지할 수 있게 되는것인데, 이런식의 코드를 만들때에는 반드시 주의해야 할 점이 있다.

Application.ProcessMessages; 프로시져로 인해, 여러 이벤트들의 Sync가 꼬일 수 있게 될 수 있다는 점이다.

예로, 사용자가 같은 버튼을 두 번 이상 빠르게 클릭하면, 마치 버튼이 재귀호출 되는 것처럼 작동할 수 있게 된다.

{ In MyForm: }
  WorkLevel :Integer;
  
{ Oncreate: }
  WorkLevel := 0;

procedure TForm1.ButtonWorkClick(Sender: TObject); 
var
  cycle : integer;
begin 
  inc(WorkLevel) ;
  for cycle := 1 to 5 do
  begin
    Memo1.Lines.Add('- Work ' + IntToStr(WorkLevel) + ', Cycle ' + IntToStr(cycle) );
 
    Application.ProcessMessages;
 
    sleep(1000) ; // or some other work
  end;
 
  Memo1.Lines.Add('Work ' + IntToStr(WorkLevel) + ' ended.') ;
 
  dec(WorkLevel) ;
end;

위 코드를 통해 버튼을 두 번 클릭 한다고 가정했을 때, Memo1에 아래와 같이 출력되기를 기대할 수 있다.

- Work 1, Cycle 1
- Work 1, Cycle 2
- Work 1, Cycle 3
- Work 1, Cycle 4
- Work 1, Cycle 5
Work 1 ended.
- Work 1, Cycle 1
- Work 1, Cycle 2
- Work 1, Cycle 3
- Work 1, Cycle 4
- Work 1, Cycle 5
Work 1 ended.

하지만, 사용자가 첫번째 클릭 이벤트가 종료되기 전에 두번째로 버튼을 빠르게 클릭하면, 아래처럼 결과가 나타날 수 있다.

- Work 1, Cycle 1
- Work 1, Cycle 2
- Work 1, Cycle 3
- Work 2, Cycle 1
- Work 2, Cycle 2
- Work 2, Cycle 3
- Work 2, Cycle 4
- Work 2, Cycle 5
Work 2 ended.
- Work 1, Cycle 4
- Work 1, Cycle 5
Work 1 ended.

Application.ProcessMessages; 프로시져에 의해서 첫번째 버튼 클릭 이벤트로 인한 Lock이 해제되면서, 이벤트 진행 도중 또다시 클릭 이벤트가 발생할 수 있게된 상황이다.
(쓰레드의 Sync가 맞지않아, 마치 버튼 클릭 이벤트가 재귀호출되는 것 처럼 보인다.)

만약, 파일 생성이나 메모리 접근 등의 작업을 처리하는 버튼이 위처럼 호출된다면 "Access Violation" 에러가 발생할 수도 있는 상황인 것이다.


결론

사실 위에서 예시로 들어준 상황은, 클릭 이벤트 코드 최상단에 ButtonWork.Enabled := False;를 써주고 이벤트 종료 직전에 True로 활성화 시켜주면 간단하게 회피할 수 있는 문제이다.

오히려 사용자로 인한 재귀호출 문제의 가능성보다, 개발자가 Event 간의, 혹은 객체간의 데이터 정합성을 고려하지 않고 작성해놓은 코드가 작동되는 중간에 Application.ProcessMessages;가 호출 되었을 때 문제가 생기는 경우가 더 치명적일 것이다.
(Sync가 어긋나면서 참조를 엉뚱한 주소로 한다거나, File을 가져올 때 값이 달라진다거나 하는 등등..)

어쨌든, Single Thread 기반의 프로그램이 어떻게 동작하는지를 잘 이해하고 있어야 Application.ProcessMessages;를 적절하게 쓰는 방법도 알게 될것 같다고 생각한다.

0개의 댓글