In my last post on using Parallel Programming and the TParallel.For construct we learned about the new System.Threading unit and how to use TParallel to make looping faster. There are however times when you need to run multiple tasks that are not loops, but these can run in parallel.
Running a number of processes in tandem has been greatly simplified with System.Threading.TTask and System.Threading.ITask
TTask provides a class to create & manage interaction with instances of ITask. You can choose to WaitForAll or WaitForAny to finish before proceeding in code.
To give an example. Imagine you have two tasks. A and B.
If A takes 3 seconds and B takes 5 seconds how long does it take to get a result to a user?
- Sequentially (without TTask / ITask) = 8 seconds.
- Using TTask.WaitForAll = 5 seconds
- Using TTask.WaitForAny = 3 seconds
Depending on what your doing, the speed for return can be dramatically quicker. So lets look at a code example for WaitForAll.
procedure TFormThreading.MyButtonClick(Sender: TObject); var tasks: array of ITask; value: Integer; begin Setlength (tasks ,2); value := 0; tasks[0] := TTask.Create (procedure () begin sleep (3000); // 3 seconds TInterlocked.Add (value, 3000); end); tasks[0].Start; tasks[1] := TTask.Create (procedure () begin sleep (5000); // 5 seconds TInterlocked.Add (value, 5000); end); tasks[1].Start; TTask.WaitForAll(tasks); ShowMessage ('All done: ' + value.ToString); end;
The above example uses an Array of ITask to process a set of tasks. The result returned is 8000, but despite 8 seconds worth of sleep commands, the first 3 seconds run in parallel, leaving the second task to finish before returning 2 seconds later, which equates to a 3 second gain on sequentially running the two tasks; and all of this without having to create your own custom threads and managing them return. 🙂
While speeding up a task to run before returning is good, you can also use TTask to prevent the user interface locking up if you want to start something in the background. To do this, you can just run a single task and start it, for example
procedure TFormThreading.Button1Click(Sender: TObject); var aTask: ITask; begin // not a thread safe snippet aTask := TTask.Create (procedure () begin sleep (3000); // 3 seconds ShowMessage ('Hello'); end); aTask.Start; end;
This second example, if used, would allow the user to press the button multiple times resulting in multiple ShowMessage calls, however, used with care this is a powerful way to run task. This is also an example of asynchronous programming where you can start the Task, get on with other stuff, and then deal with the result as it returns.
ITask
ITasks provide a range of methods and properties to Start, Wait, Cancel and also a property for Status (Created, WaitingToRun, Running, Completed, WaitingForChildren, Canceled, Exception)
As ITask is an interface, you can always create your own classes that use ITask if you so wish, providing great flexibility to the frame work.
An additional link from the Embarcadero Community for using TTask with C++ using Lambdas.
http://community.embarcadero.com/index.php/article/technical-articles/1044-coderage-9-snippet-consuming-object-pascal-code-from-c-apps-using-c-11-lambdas
HI
Can you please provide an example using a recordset as parameters to the task
something like
[code]
var
tasks: array of ITask;
qryCustomer.FetchAll;
qryCustomer.open;
Setlength (tasks ,qryCustomer.recordcount);
while not qryCustomer.eof do begin
tasks[qryCustomer.RecNo-1] := TTask.Create (
procedure
begin
//Call another proc here …is this safe??
SendEmailCustomer(qryCustomer.CodCustomer.asinteger);
end);
tasks[qryCustomer.RecNo-1].start;
qryCustomer.next;
end;
TTask.WaitForAll(tasks);
showmessage(‘All done’);[/code]
Hi Rodrigo, you could just get the customer ID and pass that in, however, if you want to take the full dataset rather than having to load it in you could always open the data in a TClientDataSet, then for each Task, create a new TClientDataSet component, and use the TClientDataSet.Clone method. This will not duplicate the data in memory but will create a separate pointer to the original data so it becomes thread safe.