Tuesday, February 03, 2015

Converting Collection to an Array

Blocking collections (IOmniBlockingCollection) are basic elements for data storage and transfer in many high-level OmniThreadLibrary abstractions. They can, however, be somewhat clumsy when you want to read data from them, as there is no indexed access, just the basic “give me next element” enumeration.

To help a bit, SVN now includes three new class functions in the TOmniBlockingCollection class – ToArray<T>, ToArrayIntf<T>, and ToArrayRec<T>.

class function ToArray<T>(coll: IOmniBlockingCollection):
TArray<T>;
class function ToArrayIntf<T: IInterface>(
  coll:
IOmniBlockingCollection): TArray<T>;

class function ToArrayRec<T: record>(
  coll:
IOmniBlockingCollection):
TArray<T>;



You can now “pump” all the data out of a blocking collection into a single TArray<T> with one call.

var
arr : TArray<integer>;
coll: IOmniBlockingCollection;
arr := TOmniBlockingCollection.ToArray<integer>(coll);

Special –Intf and –Rec versions are needed when you want to extract interfaces or records. Sadly, I couldn’t find a single way that would encompass all three functions in a single API.


There’s just one thing that you have to be aware of – ToArray will exit when it is unable to read any more data from the collection. In other words, somebody has to call CompleteAdding on a collection for ToArray to return. Demo code in the new 61_CollectionToArray demo does just that.

for i := 1 to CTestSize do
coll.Add(TOmniValue.FromRecord<TTestRec>(
TTestRec.Create(i,
IntToStr(i))));
coll.CompleteAdding;

arr := TOmniBlockingCollection.ToArrayRec<TTestRec>(coll);

You can also call ToArray before the collection is completed. That way the code can read from collection and fill up the array while the data in the collection is being generated in another thread.

procedure TfrmTestCollectionToArray.btnTestAsyncClick(Sender: TObject);
var
coll: TOmniBlockingCollection;
arr: TArray<integer>;
begin
//ToArray<T> converter with background data generation
coll := TOmniBlockingCollection.Create;

Parallel.Async(
procedure
var
i: integer;
begin
for i := 1 to CTestSize do
if IsPrime(i) then
coll.Add(i);
coll.CompleteAdding; //this will stop ToArray
end);

//conversion will run in parallel with the generator above
arr :=
TOmniBlockingCollection.ToArray<integer>(coll);
//convertor will only exit when collection is 'completed'
//(i.e. when Parallel.Async finishes its job)

lbLog.Items.Add(Format(
'%d primes, first five are %d, %d, %d, %d, %d',

[Length(arr), arr[0], arr[1], arr[2], arr[3], arr[4
]]));
end
;

[Added 2015-02-04]


With the help from Stefan Glienke (thanks!) I’ve managed to merge all functionality into a single method – ToArray<T>.

No comments:

Post a Comment