I have received quite a few mails recently, from people telling me, how I made their life easier using my code. I am really glad some of you find my code useful and easy enough to use. I made it public in case someone finds it useful.

I also made demo applications for most parts of the Cromis library, but one unit has almost no documentation and a lot of hidden content. This is the Cromis.Threading unit. Part of why this is so, is because this unit was made as a helper unit for Cromis.IPC. It contains the task (thread) pool that is used by Cromis.IPC. I needed my own lightweight implementation of a task pool so I wrote one. Then with time some other functionality regarding threading came into this unit. Mostly because I needed it here or there. But the side effects  of this are that this functionality is not documented and probably not so easy to use for someone who is not very familiar with the code. The e-mails I got recently just prove that. So I decided I will quickly write a few examples of how to use the code and show all of the functions that this unit provides.

TTaskPool

This is the most obvious class that gives you control over a pool of tasks (threads). You start using it like this

procedure TfMain.btnStartClick(Sender: TObject);
var
  Task: ITask;
begin
  FTaskPool.DynamicSize := cbDynamicPoolSize.Checked;
  FTaskPool.MinPoolSize := StrToInt(ePoolSize.Text);
  FTaskPool.OnTaskMessage := OnTaskMessage;
  FTaskPool.Initialize;
 
  tmPoolStatus.Enabled := False;
  btnStart.Enabled := False;
  btnStop.Enabled := True;
  FTerminate := False;
 
  while not FTerminate do
  begin
    Task := FTaskPool.AcquireTask(OnTaskExecute, 'RandomTask');
    Task.Values.Ensure('RandomNumber').AsInteger := Random(tbThreadTimeout.Position);
    Task.Run;
 
    pbPoolSize.Position := FTaskPool.PoolSize - FTaskPool.FreeTasks;
    stFreeThreadsValue.Caption := IntToStr(FTaskPool.FreeTasks);
    stPoolSizeValue.Caption := IntToStr(FTaskPool.PoolSize);
    Sleep(Random(tbCreationTimeout.Position));
    Application.ProcessMessages;
  end;
end;

You have two important properties here that I will explain:

DynamicSize:

This boolean property controls if the size of the pool is dynamic. Let me explain. If you start with MinPoolSize of 20 and DynamicSize is FALSE then when all 20 threads are used, the pool will assign a new thread for each request it needs. So it will adjust to the peak load of the pool. But it will then stay at that peak number of threads. If your peak is at 60 it will stay there even if the load will then drop. But if DynamicSize is TRUE it will destroy unneeded threads until you again have the 20 (MinPoolSize) of threads. In other words it will dynamically adjust to the load. Each may have its uses.

MinPoolSize:

This one is simple. It is the number of threads you start with. You cannot have less then MinPoolSize of threads in the pool.

Ok now lets look at other parts of the workings of the pool. First is when the each task is executed:

procedure TfMain.OnTaskExecute(const Task: ITask);
var
  Interval: Integer;
begin
  Interval := Task.Values.Get('RandomNumber').AsInteger;
  try
    Task.Message.Ensure('Result').AsInteger := Interval;
    Sleep(Interval);
  finally
    Task.SendMessageAsync;
  end;
end;

And the second is processing the messages that tasks send back:

procedure TfMain.OnTaskMessage(const Msg: ITaskMessage);
var
  Interval: Integer;
begin
  Inc(FTaskCounter);
  Interval := Msg.Values.Get('Result').AsInteger;
  stThreadsFinishedValue.Caption := IntToStr(FTaskCounter);
end;

As you can see all is very straightforward. Before the task is run, you fill in the values of the task and then run it. You write the code for each task and each task can send back messages to the main thread. You can do that in two ways:

Task.SendMessageAsync;
Task.SendMessageSync;

Each one speaks for itself.

Let me be clear here. This is a simple implementation of the task pool build for my internal needs. Some find it usefull and that is great. But it is in no way comparable to the OmniThreadLibrary.

TThreadSafeQueue

This is a simple implementation of the thread safe Queue, that uses locking. It is very spartan and fast.  Gabr wrote about it doing tests, some time ago:

http://www.thedelphigeek.com/2011/05/lock-free-vs-locking.html
http://www.thedelphigeek.com/2011/06/lock-free-vs-locking-rematch.html

The usage is very straightforward so no need to write about that.

TLockFreeStack

This class is a simple wrapper around the windows API and it enables the use of lock free stack.

 

ITaskQueue

This is a task queue that enables you to queue tasks even if the are run in multiple threads. This will ensure that your tasks will be executed in order that you want. The usage is very simple:

You create it like this:

  FTaskQueue := AcquireTaskQueue;

Then you enqueue

  FTaskQueue.EnqueueTask.WaitFor;

and dequeue

  FTaskQueue.DequeueTask;