2013-05-08 2 views
2

아래는 '진행'형식 코드의 일부입니다.
(코드에서 제거 된) ProgressBars 외에도 캡션 (점의 수가 증가)을 변경하려는 TLabel (LblDots)이 있습니다.
FormShow/FormClose에서 TDotterThread가 만들어지고 소멸됩니다.
열려있는 넌 모달 형식에서 스레드가 실행되지 않음

문제 :
나는 프로그램이 무거운 일을하지 않을 경우에만 호출되는 라벨을 업데이트하는 동기화 (DoUpdate) 절차를 참조하십시오. 내가 잘못 뭐하는 거지

procedure TFrmTest.FormCreate(Sender: TObject); 
begin 
    FFormBusy := TFormBusy.Create(nil); 
end; 

procedure TFrmTest.FormDestroy(Sender: TObject); 
begin 
    FFormBusy.Free; 
end; 

procedure TFrmTest.BtnCompareClick(Sender: TObject); 
begin 
    FrmTest.FFormBusy.ShowDots := true; 
    FrmTest.FFormBusy.Show; 
    FrmTest.FFormBusy.Update label/progress bar 
    DoHeavyWork1(); 
    FrmTest.FFormBusy.Update label/progress bar 
    DoHeavyWork2(); 
    etc. 
end;  

:

unit FrmBusy; 

interface 

uses 
    System.SyncObjs, Windows, Messages, SysUtils, System.Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; 

type 
    TUpdateEvent = procedure of object; // 'of object' to prevent 'Incompatible types: regular procedure and method pointer' 

type 
    TDotterThread = class(TThread)   // Thread to update LblDots 
    private 
     FTick: TEvent; 
     FUpdater: TUpdateEvent; 
    protected 
     procedure Execute; override; 
     procedure DoUpdate; 
    public 
     constructor Create; 
     destructor Destroy; override; 
     property Updater: TUpdateEvent read FUpdater write FUpdater; 
     procedure Stop; 
    end; 

type 
    TFormBusy = class(TForm) 
    LblDots: TLabel; 
    procedure FormShow(Sender: TObject); 
    procedure FormClose(Sender: TObject; var Action: TCloseAction); 
    private 
    FShowDots: Boolean; 
    FDotterThread: TDotterThread; 
    procedure UpdateDots; 
    public 
    property ShowDots: Boolean write FShowDots; 
    end; 

implementation 

{$R *.DFM} 

procedure TFormBusy.FormClose(Sender: TObject; var Action: TCloseAction); 
begin 
    if FShowDots then FDotterThread.Stop; // Calls Terminate and is FreeOnTerminate 
end; 

procedure TFormBezig.UpdateDots; 
var s: String; 
begin 
    s := LblDots.Caption; 
    if Length(s) = 50 then s := '' else s := s + '.'; 
    LblDots.Caption := s; 
    Application.ProcessMessages; 
end; 

procedure TFormBusy.FormShow(Sender: TObject); 
begin 
    LblDots.Caption := ''; 
    if FShowDots then 
    begin 
     FDotterThread := TDotterThread.Create; 
     FDotterThread.Updater := Self.UpdateDots; 
     FDotterThread.Start; 
    end; 
    BringWindowToTop(Self.Handle); 
end; 

{ TDotterThread } 

constructor TDotterThread.Create; 
begin 
    FTick := TEvent.Create(nil, True, False, ''); 
    FreeOnTerminate := true; 
    inherited Create(true); // Suspended 
end; 

destructor TDotterThread.Destroy; 
begin 
    FTick.Free; 
    inherited; 
end; 

procedure TDotterThread.DoUpdate; 
begin 
    if Assigned(FUpdater) then FUpdater; 
end; 

procedure TDotterThread.Execute; 
begin 
    while not Terminated do 
    begin 
    FTick.WaitFor(1000); 
    Synchronize(DoUpdate); 
    end; 
end; 

procedure TDotterThread.Stop; 
begin 
    Terminate; 
    FTick.SetEvent; 
end; 

end. 

양식은 전화를 같이 작성됩니다

이 진행 형태이다?
TIA

+4

DoUpdate는 동기화되어야하기 때문에 메인 스레드의 컨텍스트에 있고 메인 스레드에 "유휴 시간"이있는 경우에만 업데이트 절차를 처리 할 수 ​​있습니다. 어쩌면 스레드 내에서 DoHeavyWorkx를 처리하도록 변경할 수 있습니다. – bummi

+0

@bummi는 정확합니다. 메소드를 동기화하여 주 스레드로 다시 밀어 넣고 전체 목적을 물리칩니다.우리에게 보여줄 수있는 중요한 절차, 그리고 수정해야 할 절차는'FFormBusy.UpdateDots'라고 생각합니다 - 그 코드를 게시 할 수 있습니까? (더 많은 점 ... 더 많은 점을 보여라!) –

+0

@J 나는 코드에서 그것을 accidenttaly 잘라내는 것을 본다 - 그것을 수정하기 위해 나의 질문을 편집했다. –

답변

2

아시다시피 모든 UI 코드는 기본 GUI 스레드에서 실행되어야합니다. 그래서 GUI를 업데이트하려면 Synchronize으로 전화하십시오. 동기화는 다음과 같이 광범위하게 작동합니다.

  1. 주 스레드에서 수행 할 작업이 대기열에 배치됩니다.
  2. 주 스레드는 동기화 작업이 보류 중임을 나타냅니다.
  3. 배경 스레드 차단.
  4. 메인 스레드가 보류중인 동기화 작업이 있는지 확인한 다음 실행합니다.
  5. 백그라운드 스레드는 작업이 실행되었음을 나타 내기 위해 신호를 보냅니다.
  6. 백그라운드 스레드가 차단을 멈추고 실행을 계속합니다.

상당히 복잡한 댄스입니다.

문제는 주 스레드가 일부 장기 실행 작업을 수행하는 중입니다. 아마도 DoHeavyWork1DoHeavyWork2으로 전화를 걸었을 것입니다. 이것은 GUI 스레드가 적시에 항목 4를 수행하지 않는다는 것을 의미합니다. 또한 메인 스레드는 백그라운드 스레드를 차단하고 스레드의 유틸리티를 무효화합니다.

기본적으로 문제는 기본 GUI 스레드가 GUI 서비스 이외의 작업을 수행하고 있다는 것입니다. GUI 서비스를 위해 GUI 스레드를 전용해야합니다. 그것은 다른 것을 취해야하며, 장기간에 걸친 작업을 수행하지 않아야합니다. 일단 GUI가 아닌 모든 작업을 GUI 스레드에서 백그라운드 스레드로 보내면 응용 프로그램이 응답 속도가 빠른 것을 알 수 있습니다.

마지막으로 Application.ProcessMessages의 전화 번호를 UpdateDots에서 삭제하는 것이 좋습니다. 응답하지 않는 GUI를 다루려고 아마 추가했을 것입니다. 그러나 문제는 UpdateDots이 적시에 실행되지 않기 때문에 전혀 도움이되지 않습니다.

+0

어쩌면 간단히 요약 해 보겠습니다. 그는 작업 스레드의 UI 업데이트를 마샬링하려하지 않고 DoHeavyWork를 TThread에 넣어야합니다. 가끔 '동기화'를 호출하여 TThread에서 실행중인 DoHeavyWork 내부에서 UI를 업데이트 할 수 있습니다. –

+0

@J ... 작업자 스레드에서 UI 업데이트를 마샬링하는 것은 GUI 스레드가 응답 가능하고 서비스 할 수있는 한 괜찮습니다. –

+0

당연히. 나는 그것이 지금 어떻게 읽히지 않는지를 안다. 나는 UI 작업 (그리고 UI 작업 만)을 작업자 스레드와 비교해 보았습니다. 무거운 작업을 수행하는 것보다 다시 마샬링해야했습니다. –

관련 문제