2012-06-30 39 views
23

Tôi luôn tự hỏi có cách nào tốt hơn là tôi nên viết một số quy trình của mình, đặc biệt là những quy trình mất nhiều thời gian để hoàn thành.Tôi có cần TThread không? Nếu vậy tôi có thể tạm dừng, tiếp tục và dừng lại không?

Tôi luôn chạy mọi thứ trong Giao diện chính của GUI mà bây giờ tôi hiểu và nhận ra là không tốt vì nó sẽ khiến Ứng dụng không hồi đáp, Application.ProcessMessages sẽ không thực sự trợ giúp ở đây.

Điều này làm cho tôi nghĩ rằng tôi cần phải sử dụng TThread cho các hoạt động dài như sao chép tệp chẳng hạn. Điều này cũng làm cho tôi tự hỏi làm thế nào một số ứng dụng cung cấp cho bạn toàn quyền kiểm soát, ví dụ như cho phép bạn tạm dừng, tiếp tục và hoặc ngừng hoạt động.

Tôi có khoảng 3 hoạt động kéo dài trong một dự án cá nhân mà tôi đang làm việc trên đó tôi hiển thị biểu mẫu hộp thoại có bật TProgressBar. Trong khi điều này làm việc, tôi cảm thấy nó có thể được thực hiện tốt hơn nhiều. Các hộp thoại tiến bộ này có thể được hiển thị trong một thời gian dài mà bạn có thể muốn hủy thao tác và thay vào đó hoàn thành công việc sau này.

Như tôi đã nói, hiện tại tôi đang chạy Đề tài chính Gui, thay vào đó tôi có cần sử dụng TThread không? Tôi không chắc chắn làm thế nào hoặc nơi để bắt đầu thực hiện chúng như tôi đã không làm việc với họ trước đây. Nếu tôi cần chủ đề họ cung cấp những gì tôi cần như tạm dừng, tiếp tục, dừng một hoạt động vv?

Về cơ bản, tôi đang tìm cách xử lý và quản lý hoạt động lâu hơn tốt hơn.

+6

Bạn cần báo hiệu cho chuỗi mà bạn muốn tạm dừng hoặc hủy. Và luồng phải kiểm tra tín hiệu đó. –

+0

hoặc bạn có thể tạm ngưng và sau đó tiếp tục. Một tín hiệu giống như một var toàn cầu là tốt hơn và có tổ chức hơn. Bạn cũng có thể sử dụng Mutex làm tín hiệu ... –

+4

@Benjamin Tạm dừng và Tiếp tục? Không hẳn. Những chức năng Windows không nên được sử dụng. –

Trả lời

16

Có, đây chắc chắn là trường hợp bạn cần một chuỗi để thực hiện tác vụ.

Ví dụ nhỏ về cách tạm dừng/tiếp tục một chuỗi và hủy chuỗi.

Tiến trình được gửi đến chuỗi chính thông qua cuộc gọi PostMessage. Tạm dừng/tiếp tục và hủy được thực hiện với các tín hiệu TSimpleEvent.

Edit: Theo ý kiến ​​từ @mghie, đây là một ví dụ hoàn chỉnh hơn:

Chỉnh sửa 2: Hiển thị làm thế nào để vượt qua một thủ tục cho thread để gọi cho các công việc nặng nhọc.

Chỉnh sửa 3: Đã thêm một số tính năng và đơn vị thử nghiệm.

unit WorkerThread; 

interface 

uses Windows, Classes, SyncObjs; 

type 
    TWorkFunction = function: boolean of object; 

    TWorkerThread = Class(TThread) 
    private 
    FCancelFlag: TSimpleEvent; 
    FDoWorkFlag: TSimpleEvent; 
    FOwnerFormHandle: HWND; 
    FWorkFunc: TWorkFunction; // Function method to call 
    FCallbackMsg: integer; // PostMessage id 
    FProgress: integer; 
    procedure SetPaused(doPause: boolean); 
    function GetPaused: boolean; 
    procedure Execute; override; 
    public 
    Constructor Create(WindowHandle: HWND; callbackMsg: integer; 
     myWorkFunc: TWorkFunction); 
    Destructor Destroy; override; 
    function StartNewWork(newWorkFunc: TWorkFunction): boolean; 
    property Paused: boolean read GetPaused write SetPaused; 
    end; 

implementation 

constructor TWorkerThread.Create(WindowHandle: HWND; callbackMsg: integer; 
    myWorkFunc: TWorkFunction); 
begin 
    inherited Create(false); 
    FOwnerFormHandle := WindowHandle; 
    FDoWorkFlag := TSimpleEvent.Create; 
    FCancelFlag := TSimpleEvent.Create; 
    FWorkFunc := myWorkFunc; 
    FCallbackMsg := callbackMsg; 
    Self.FreeOnTerminate := false; // Main thread controls for thread destruction 
    if Assigned(FWorkFunc) then 
    FDoWorkFlag.SetEvent; // Activate work at start 
end; 

destructor TWorkerThread.Destroy; // Call MyWorkerThread.Free to cancel the thread 
begin 
    FDoWorkFlag.ResetEvent; // Stop ongoing work 
    FCancelFlag.SetEvent; // Set cancel flag 
    Waitfor; // Synchronize 
    FCancelFlag.Free; 
    FDoWorkFlag.Free; 
    inherited; 
end; 

procedure TWorkerThread.SetPaused(doPause: boolean); 
begin 
    if doPause then 
    FDoWorkFlag.ResetEvent 
    else 
    FDoWorkFlag.SetEvent; 
end; 

function TWorkerThread.StartNewWork(newWorkFunc: TWorkFunction): boolean; 
begin 
    Result := Self.Paused; // Must be paused ! 
    if Result then 
    begin 
    FWorkFunc := newWorkFunc; 
    FProgress := 0; // Reset progress counter 
    if Assigned(FWorkFunc) then 
     FDoWorkFlag.SetEvent; // Start work 
    end; 
end; 

procedure TWorkerThread.Execute; 
{- PostMessage LParam: 
    0 : Work in progress, progress counter in WParam 
    1 : Work is ready 
    2 : Thread is closing 
} 
var 
    readyFlag: boolean; 
    waitList: array [0 .. 1] of THandle; 
begin 
    FProgress := 0; 
    waitList[0] := FDoWorkFlag.Handle; 
    waitList[1] := FCancelFlag.Handle; 
    while not Terminated do 
    begin 
    if (WaitForMultipleObjects(2, @waitList[0], false, INFINITE) <> 
     WAIT_OBJECT_0) then 
     break; // Terminate thread when FCancelFlag is signaled 
    // Do some work 
    readyFlag := FWorkFunc; 
    if readyFlag then // work is done, pause thread 
     Self.Paused := true; 
    Inc(FProgress); 
    // Inform main thread about progress 
    PostMessage(FOwnerFormHandle, FCallbackMsg, WPARAM(FProgress), 
     LPARAM(readyFlag)); 
    end; 
    PostMessage(FOwnerFormHandle, FCallbackMsg, 0, LPARAM(2)); // Closing thread 
end; 

function TWorkerThread.GetPaused: boolean; 
begin 
    Result := (FDoWorkFlag.Waitfor(0) <> wrSignaled); 
end; 

end. 

Chỉ cần gọi MyThread.Paused := true để tạm dừng và MyThread.Paused := false để tiếp tục thao tác chuỗi.

Để hủy chuỗi, hãy gọi MyThread.Free.

Tiếp nhận các thông điệp gửi từ các chủ đề, xem ví dụ sau:

unit Unit1; 

interface 

uses 
    Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, 
    System.Classes, Vcl.Graphics, 
    Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, WorkerThread; 

const 
    WM_MyProgress = WM_USER + 0; // The unique message id 

type 
    TForm1 = class(TForm) 
    Label1: TLabel; 
    btnStartTask: TButton; 
    btnPauseResume: TButton; 
    btnCancelTask: TButton; 
    Label2: TLabel; 
    procedure btnStartTaskClick(Sender: TObject); 
    procedure btnPauseResumeClick(Sender: TObject); 
    procedure btnCancelTaskClick(Sender: TObject); 
    private 
    { Private declarations } 
    MyThread: TWorkerThread; 
    workLoopIx: integer; 

    function HeavyWork: boolean; 
    procedure OnMyProgressMsg(var Msg: TMessage); message WM_MyProgress; 
    public 
    { Public declarations } 
    end; 

var 
    Form1: TForm1; 

implementation 

{$R *.dfm} 

{ TForm1 } 
const 
    cWorkLoopMax = 500; 

function TForm1.HeavyWork: boolean; // True when ready 
var 
    i, j: integer; 
begin 
    j := 0; 
    for i := 0 to 10000000 do 
    Inc(j); 
    Inc(workLoopIx); 
    Result := (workLoopIx >= cWorkLoopMax); 
end; 

procedure TForm1.btnStartTaskClick(Sender: TObject); 
begin 
    if not Assigned(MyThread) then 
    begin 
    workLoopIx := 0; 
    btnStartTask.Enabled := false; 
    btnPauseResume.Enabled := true; 
    btnCancelTask.Enabled := true; 
    MyThread := TWorkerThread.Create(Self.Handle, WM_MyProgress, HeavyWork); 
    end; 
end; 

procedure TForm1.btnPauseResumeClick(Sender: TObject); 
begin 
    if Assigned(MyThread) then 
    MyThread.Paused := not MyThread.Paused; 
end; 

procedure TForm1.btnCancelTaskClick(Sender: TObject); 
begin 
    if Assigned(MyThread) then 
    begin 
    FreeAndNil(MyThread); 
    btnStartTask.Enabled := true; 
    btnPauseResume.Enabled := false; 
    btnCancelTask.Enabled := false; 
    end; 
end; 

procedure TForm1.OnMyProgressMsg(var Msg: TMessage); 
begin 
    Msg.Msg := 1; 
    case Msg.LParam of 
    0: 
     Label1.Caption := Format('%5.1f %%', [100.0 * Msg.WParam/cWorkLoopMax]); 
    1: 
     begin 
     Label1.Caption := 'Task done'; 
     btnCancelTaskClick(Self); 
     end; 
    2: 
     Label1.Caption := 'Task terminated'; 
    end; 
end; 

end. 

Và có dạng:

object Form1: TForm1 
    Left = 0 
    Top = 0 
    Caption = 'Form1' 
    ClientHeight = 163 
    ClientWidth = 328 
    Color = clBtnFace 
    Font.Charset = DEFAULT_CHARSET 
    Font.Color = clWindowText 
    Font.Height = -13 
    Font.Name = 'Tahoma' 
    Font.Style = [] 
    OldCreateOrder = False 
    PixelsPerInch = 120 
    TextHeight = 16 
    object Label1: TLabel 
    Left = 79 
    Top = 18 
    Width = 51 
    Height = 16 
    Caption = 'Task idle' 
    end 
    object Label2: TLabel 
    Left = 32 
    Top = 18 
    Width = 41 
    Height = 16 
    Caption = 'Status:' 
    end 
    object btnStartTask: TButton 
    Left = 32 
    Top = 40 
    Width = 137 
    Height = 25 
    Caption = 'Start' 
    TabOrder = 0 
    OnClick = btnStartTaskClick 
    end 
    object btnPauseResume: TButton 
    Left = 32 
    Top = 71 
    Width = 137 
    Height = 25 
    Caption = 'Pause/Resume' 
    Enabled = False 
    TabOrder = 1 
    OnClick = btnPauseResumeClick 
    end 
    object btnCancelTask: TButton 
    Left = 32 
    Top = 102 
    Width = 137 
    Height = 25 
    Caption = 'Cancel' 
    Enabled = False 
    TabOrder = 2 
    OnClick = btnCancelTaskClick 
    end 
end 
+1

Điều này thực sự không tuyệt vời như mã ví dụ. Chủ đề bị tạm dừng của bạn sẽ vẫn thức dậy vài chục lần một giây. Và làm thế nào bạn sẽ đồng bộ hóa thread tắt máy và phá hủy các đối tượng sự kiện bên ngoài sở hữu? Đơn giản chỉ cần báo hiệu cho họ, chờ một chút và hy vọng điều tốt nhất? Bạn cần phải sử dụng tính tham chiếu cho các đối tượng được chia sẻ hoặc giải phóng các chủ đề trước khi các sự kiện được giải phóng (tức là không sử dụng 'FreeOnTerminate') để làm điều này một cách thích hợp. – mghie

+0

@mghie, bạn đúng, tôi đã làm ví dụ hoàn chỉnh hơn. Các chủ đề bây giờ là nhiều hơn không hoạt động trong trạng thái tạm dừng và chủ đề chính điều khiển thời gian cuộc sống của chủ đề. –

+1

Tốt hơn nhiều, +1 từ tôi. – mghie

0

Giới thiệu hữu ích về đa luồng được viết bởi một người tên là Martin Harvey, nhiều năm trước. Hướng dẫn của ông có thể được tìm thấy tại Embarcadero CC site - nó cũng giống như ông đã tải lên một lớp ví dụ mà loại điều bạn đang tìm kiếm, nhưng tôi đã không nhìn vào nó vì vậy không thể nói chắc chắn.

3

Nếu mã mẫu trong answer by LU RD quá phức tạp đối với khẩu vị của bạn thì có thể a Delphi implementation of the .net BackgroundWorker class là theo ý thích của bạn.

Sử dụng điều này bạn có thể thả một thành phần vào biểu mẫu của bạn và thêm trình xử lý cho các sự kiện khác nhau của nó (OnWork, OnWorkProgress, OnWorkFeedbackOnWorkComplete). Thành phần sẽ thực thi trình xử lý sự kiện OnWork ở chế độ nền, trong khi thực hiện các trình xử lý sự kiện khác từ luồng GUI (quản lý các công tắc ngữ cảnh và đồng bộ hóa cần thiết). Tuy nhiên, một sự hiểu biết thấu đáo về những gì bạn có thể và những gì bạn không phải làm từ các luồng thứ cấp vẫn cần thiết để viết mã trong trình xử lý sự kiện OnWork.

Các vấn đề liên quan