수색…


백그라운드 작업을위한 스레드를 사용하는 반응적인 GUI와 스레드에서 다시보고하기위한 PostMessage

긴 프로세스를 실행하는 동안 GUI 응답을 유지하려면 GUI가 메시지 대기열을 처리 할 수 ​​있도록하는 매우 정교한 "콜백"또는 (백그라운드) (작업자) 스레드 사용이 필요합니다.

몇 가지 작업을 수행하는 데 필요한 스레드 수를 늘리면 문제가되지 않습니다. GUI가 중간 결과와 최종 결과를 보여 주거나 진행 상황을보고 싶을 때 재미있는 일이 시작됩니다.

GUI에 무엇이든 표시하려면 컨트롤 및 / 또는 메시지 대기열 / 펌프와 상호 작용해야합니다. 항상 메인 스레드의 컨텍스트에서 수행되어야합니다. 다른 스레드의 컨텍스트에서는 절대로 사용하지 마십시오.

이것을 처리 할 수있는 방법은 여러 가지가 있습니다.

이 예는 FreeOnTerminatefalse 로 설정하여 GUI가 스레드 인스턴스에 액세스 할 수있게하고 스레드가 PostMessage 사용하여 "완료"되었을 때 간단한 스레드를 사용하여 스레드 인스턴스에 액세스하는 방법을 보여줍니다.

경쟁 조건에 대한 참고 사항 : 작업자 스레드에 대한 참조는 양식의 배열로 유지됩니다. 스레드가 완료되면 배열의 해당 참조가 nil-ed가됩니다.

이는 경쟁 조건의 잠재적 원천입니다. 끝내야하는 스레드가 있는지 여부를 쉽게 판별 할 수 있도록 "실행 중"부울을 사용합니다.

잠금 장치를 사용하여이 리소스를 보호해야하는지 여부를 결정해야합니다.

이 예에서는 그대로 서있을 필요가 없습니다. StartThreads 메서드와 HandleThreadResults 메서드의 두 위치에서만 수정됩니다. 두 가지 방법 모두 주 스레드의 컨텍스트에서 실행됩니다. 그런 식으로 유지하고 다른 스레드의 컨텍스트에서 이러한 메서드를 호출하지 않는 한, 경쟁 조건을 생성 할 수있는 방법이 없습니다.

type
  TWorker = class(TThread)
  private
    FFactor: Double;
    FResult: Double;
    FReportTo: THandle;
  protected
    procedure Execute; override;
  public
    constructor Create(const aFactor: Double; const aReportTo: THandle);

    property Factor: Double read FFactor;
    property Result: Double read FResult;
  end;

생성자는 private 멤버를 설정하고 FreeOnTerminate를 False로 설정합니다. 주 스레드가 결과에 대한 스레드 인스턴스를 쿼리 할 수 ​​있으므로 필수적입니다.

execute 메소드는 계산을 수행 한 다음 생성자에서 수신 한 핸들에 메시지를 게시하여 done을 말합니다.

procedure TWorker.Execute;
const
  Max = 100000000;var
  i : Integer;
begin
  inherited;

  FResult := FFactor;
  for i := 1 to Max do
    FResult := Sqrt(FResult);

  PostMessage(FReportTo, UM_WORKERDONE, Self.Handle, 0);
end;

PostMessage 의 사용은이 예제에서 필수적입니다. PostMessage "그냥"주 스레드의 메시지 펌프 큐에 메시지를 넣고 처리 될 때까지 기다리지 않습니다. 그것은 본질적으로 비동기 적입니다. SendMessage 를 사용한다면 자신을 피클로 코딩하게 될 것입니다. SendMessage 는 메시지를 큐에 넣고 처리 될 때까지 대기합니다. 즉, 그것은 동기식입니다.

사용자 지정 UM_WORKERDONE 메시지에 대한 선언은 다음과 같이 선언됩니다.

const
  UM_WORKERDONE = WM_APP + 1;
type
  TUMWorkerDone = packed record
    Msg: Cardinal;
    ThreadHandle: Integer;
    unused: Integer;
    Result: LRESULT;
  end;

UM_WORKERDONE const는 WM_APP 를 값의 시작점으로 사용하여 Windows 또는 Delphi VCL에서 사용되는 값 (MicroSoft에서 권장 하는 값)을 방해하지 않도록합니다.

형태

모든 양식을 사용하여 스레드를 시작할 수 있습니다. 다음 멤버를 추가하기 만하면됩니다.

private
  FRunning: Boolean;
  FThreads: array of record
    Instance: TThread;
    Handle: THandle;
  end;
  procedure StartThreads(const aNumber: Integer);
  procedure HandleThreadResult(var Message: TUMWorkerDone); message UM_WORKERDONE;

아, 그리고 예제 코드는 Memo1: TMemo; 의 존재를 가정합니다 Memo1: TMemo; 폼의 선언에서 "로깅 및보고"에 사용합니다.

FRunning 은 작업이 진행되는 동안 GUI가 클릭되기 시작하지 못하게하는 데 사용될 수 있습니다. FThreads 는 인스턴스 포인터와 생성 된 스레드의 핸들을 유지하는 데 사용됩니다.

스레드를 시작하는 절차는 매우 간단합니다. 대기중인 스레드 세트가 이미 있는지 여부를 확인하는 것으로 시작합니다. 그렇다면 그냥 종료됩니다. 그렇지 않은 경우, 플래그를 true로 설정하고 각각의 핸들을 제공하는 스레드를 시작하여 "완료"메시지를 어디에 게시 할지를 알 수 있습니다.

procedure TForm1.StartThreads(const aNumber: Integer);
var
  i: Integer;
begin
  if FRunning then
    Exit;
    
  FRunning := True;

  Memo1.Lines.Add(Format('Starting %d worker threads', [aNumber]));
  SetLength(FThreads, aNumber);
  for i := 0 to aNumber - 1 do
  begin
    FThreads[i].Instance := TWorker.Create(pi * (i+1), Self.Handle);
    FThreads[i].Handle := FThreads[i].Instance.Handle;
  end;
end;

쓰레드 핸들은 또한 쓰레드가 완료되었음을 알리는 메시지에서 우리가받는 것이기 때문에 배열에 저장됩니다. 스레드의 인스턴스 외부에서 핸들을 사용할 수있게하면 결과를 얻기 위해 인스턴스가 필요하지 않으면 True 설정된 FreeOnTerminate 를 사용할 수 있습니다 (예 : 데이터베이스에 저장된 경우). 이 경우 인스턴스에 대한 참조를 유지할 필요가 없습니다.

재미있는 부분은 HandleThreadResult 구현입니다.

procedure TForm1.HandleThreadResult(var Message: TUMWorkerDone);
var
  i: Integer;
  ThreadIdx: Integer;
  Thread: TWorker;
  Done: Boolean;
begin
  // Find thread in array
  ThreadIdx := -1;
  for i := Low(FThreads) to High(FThreads) do
    if FThreads[i].Handle = Cardinal(Message.ThreadHandle) then
    begin
      ThreadIdx := i;
      Break;
    end;

  // Report results and free the thread, nilling its pointer and handle 
  // so we can detect when all threads are done.
  if ThreadIdx > -1 then
  begin
    Thread := TWorker(FThreads[i].Instance);
    Memo1.Lines.Add(Format('Thread %d returned %f', [ThreadIdx, Thread.Result]));
    FreeAndNil(FThreads[i].Instance);
    FThreads[i].Handle := nil;
  end;

  // See whether all threads have finished.
  Done := True;
  for i := Low(FThreads) to High(FThreads) do
    if Assigned(FThreads[i].Instance) then
    begin
      Done := False;
      Break;
    end;
  if Done then
  begin
    Memo1.Lines.Add('Work done');
    FRunning := False;
  end;
end;

이 메서드는 먼저 메시지에서받은 핸들을 사용하여 스레드를 찾습니다. 일치하는 것이 발견되면 인스턴스를 사용하여 스레드의 결과를 검색하고보고합니다 ( FreeOnTerminateFalse .)? 인스턴스를 해제하고 인스턴스 참조와 핸들을 모두 nil로 설정하여이 스레드가 아니요임을 나타냅니다. 더 긴 관련성.

마지막으로 스레드가 계속 실행 중인지 확인합니다. 아무 것도 없으면 "all done"이보고되고 FRunning 플래그는 False 설정되어 새로운 작업 배치를 시작할 수 있습니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow