Monday, June 30, 2008

OmniThreadLibrary Example #1: Beep, world!

image In my previous post, I promised to publish some examples of OTL usage. Here is the first one - a simplification of the Hello, World program. This example doesn't write anything to the screen, it just makes a noise.

Start by creating a new Delphi VCL application. Drop a button on the form and write OnClick handler.

procedure TfrmTestOTL.btnBeepClick(Sender: TObject);
begin
CreateTask(Beep, 'Beep').Run;
end;

This will create a threaded task with name 'Beep' and main method Beep. Then the code will create a new thread and start executing this task in the context of the newly created thread. That's all, folks!


To make the example fully functional, add OtlTask unit to the uses list and write the Beep method.

procedure TfrmTestOTL.Beep(task: IOmniTask);
begin
MessageBeep(MB_ICONEXCLAMATION);
end;

Run the program, click the button. It will beep!


If you don't believe that the code is executed in a secondary thread, place a breakpoint on the MessageBeep call and check the Thread Status window. It will clearly indicate that the breakpoint was triggered from a secondary thread.


image


This example is included in the OTL repository in folder tests/0_Beep.

11 comments:

  1. "This project currently has no downloads."----from google code

    ReplyDelete
  2. Anonymous08:23

    True. At this moment, you can only do SVN checkout. Instructions are on the Source tab.

    ReplyDelete
  3. Anonymous08:36

    You could use this function to make the thread's name show up in the thread list:

    procedure SetThreadName(const _Name: string);
    var
    ThreadNameInfo: TThreadNameInfo;
    begin
    ThreadNameInfo.FType := $1000;
    ThreadNameInfo.FName := PChar(_Name);
    ThreadNameInfo.FThreadID := $FFFFFFFF;
    ThreadNameInfo.FFlags := 0;
    try
    RaiseException($406D1388, 0, SizeOf(ThreadNameInfo) div SizeOf(LongWord), @ThreadNameInfo);
    except
    // ignore
    end;
    end;

    twm

    ReplyDelete
  4. That is not a bad idea.

    I had problems with such code in older Delphis. I don't remember it well, but I think that the debugger stopped on this RaiseException when I was single-stepping through the code. I'll have to try it in D2007 ...

    ReplyDelete
  5. New version of the OtlTask unit with SetThreadName support was just uploaded to the Google Code.

    ReplyDelete
  6. Anonymous10:42

    Amazing threading approach.
    I really love it.

    ReplyDelete
  7. Thanks, I'm glad you like it.

    Spread the good word around :)

    ReplyDelete
  8. Hi,

    I have tried to make it beeps more than once, so I have modified the procedure Beep to:

    for I := 0 to 30 do
    begin
    MessageBeep(MB_ICONEXCLAMATION);
    Sleep(1000);
    end;

    But then I have found that the demo is not responding until the beeping finished.

    What have I done wrong?

    Bo Chen Lin

    ReplyDelete
  9. That is because in this simple test, interface returned from the CreateTask is not stored anywhere.

    procedure TfrmTestSimple.btnBeepClick(Sender: TObject);
    begin
    CreateTask(Beep, 'Beep').Run;
    end;

    Therefore, this interface is destroyed when btnBeepClick exits. When the task control's destructor is called, it waits for the task to complete and that's causing the blockage.

    You should store result of the CreateTask in a form field, as the next demos do.

    ReplyDelete
  10. Taking your suggestion, I added FThread to hold the interface:

    procedure TfrmTestSimple.btnBeepClick(Sender: TObject);
    begin
    FThread := CreateTask(Beep, 'Beep').Run;
    end;

    Not it is responding with the beeps.

    But there is a problem again, it won't close until the beep finished. So I thought the thread should be terminated when the form was closing:

    procedure TfrmTestSimple.FormClose(Sender: TObject; var Action: TCloseAction);
    begin
    FThread.Terminate(1000);
    end;

    then the demo can be closed without having to wait all the beeps finished. This is all good until I added FastMM4 in the project file. It
    reports some memory leaks. I thought it might due to that FThread is not cleared. So I managed to clear the interface reference by:

    destructor TfrmTestSimple.Destroy;
    begin
    FThread := nil;
    inherited;
    end;

    This reduced the memory leaks, but there are still two leaks of TOmniTask and TOmniThread.

    What I don't understand is that if I let the 30 beeps finished, then there is no memory leak.

    Why?

    ReplyDelete
  11. This is all as expected. The demo won't close because the background thread is waiting in the for/sleep. Terminate will kill it but that will lead to lost memory.

    Please study other OTL examples to see how the background thread can be properly implemented.

    ReplyDelete