Tasks

Let's get back to business, and in this chapter business means tasks. I don't want to spend all the time talking about the theory and classes and interfaces, so I will start by introducing some code. Actually, it is not my code—I got it from our good friend, Mr. Smith.

For the past few chapters, he was quite busy, working on things botanical, so I let him be. (He gave me a comprehensive overview of his activities, but I'm just a programmer, and I didn't really understand him.) Now he has more spare time, and he returned to his great love—prime numbers. He is no longer trying to improve SlowCode. Now he studies how the probability of finding a prime number changes when numbers become larger. To do so, he wrote two simple functions (shown as follows) that check a range of numbers and count how many prime numbers are in this range:

function IsPrime(value: integer): boolean;
var
i: Integer;
begin
Result := (value > 1);
if Result then
for i := 2 to Round(Sqrt(value)) do
if (value mod i) = 0 then
Exit(False);
end;

function FindPrimes(lowBound, highBound: integer): integer;
var
i: Integer;
begin
  Result := 0;
for i := lowBound to highBound do
if IsPrime(i) then
Inc(Result);
end;

He soon found out that his code is using only one CPU on his 64-core machine. As he found out that I'm writing about parallel programming, he asked me to change his code so that all the CPUs will be used. I didn't have the heart to tell him that his approach is flawed from the beginning and that he should use mathematical theory to solve his problem. Rather, I took his code and used it as a starting point for this chapter. There are different ways to parallelize such code. It probably won't surprise you that I'll start with the most basic one, tasks.

In Delphi's Parallel Programming Library, tasks are objects that implement the ITask interface. They are created through methods of the TTask class. Both the class and the interface are implemented in the System.Threading unit.

The code in the ParallelTasks demo shows basic operations on the tasks. The following method is executed if you click on the Run tasks button.

The first task is created by calling the TTask.Run method. This method creates a new task and immediately starts it. The task will be started in a thread, run the SleepProc code, and terminate. Run returns an ITask interface, which we can use to query and manipulate the task.

The second task is created by calling the TTask.Create method. This method creates a new task but does not start it. It returns an ITask interface, just as Run does. In this second example, the task payload is a simple anonymous method. As we want it to start running immediately, the code then calls the ITask.Start method, which starts the task. Start returns the task's ITask interface, which the code saves in the tasks array.

The code then waits for both tasks to complete by calling the TTask.WaitForAll method. It will wait until both the anonymous method and SleepProc exit. The program will be blocked for about 2.5 seconds in WaitForAll, which is not optimal. Later we'll look into different techniques to work around the problem:

procedure SleepProc;
begin
Sleep(2500);
end;

procedure TfrmParallelTasks.btnRunTasksClick(Sender: TObject);
var
tasks: array [1..2] of ITask;
begin
tasks[1] := TTask.Run(SleepProc);
tasks[2] := TTask.Create(procedure begin Sleep(2000) end).Start;
TTask.WaitForAll(tasks);
end;

If we don't want to block the main program, we have to use a different technique to find out when the task has finished its work. We can apply any of the notification methods from the previous chapter—Windows messages, Synchronize, Queue, or polling. In this example, I used the Queue.

When you click the Async TTask button in the ParallelTasks demo, the btnAsyncTaskClick method (shown as follows) is executed. This method creates a task by calling TTask.Run and stores the task interface into the form field FTask: ITask. The code also disables the button to give a visual indication that the task is running.

The task then executes the LongTask method in its own thread. After the hard work (Sleep) is done, it queues the notification method, LongTaskCompleted, to the main thread. This method cleans up after the finished task by setting the FTask field to nil and reactivates the btnAsyncTask button:

procedure TfrmParallelTasks.LongTask;
begin
Sleep(2000);
TThread.Queue(nil, LongTaskCompleted);
end;

procedure TfrmParallelTasks.LongTaskCompleted;
begin
FTask := nil;
btnAsyncTask.Enabled := True;
end;

procedure TfrmParallelTasks.btnAsyncTaskClick(Sender: TObject);
begin
FTask := TTask.Run(LongTask);
btnAsyncTask.Enabled := False;
end;

When you have to wait for multiple tasks to complete, the code becomes a bit more complicated. You have to keep count of running tasks and only trigger the notification when the last task completes. You'll have to wait a bit for the practical example. Later in this chapter, I'll implement a custom join pattern, and I'll return to the notification problem then.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.136.249