Jump to content
David Schwartz

Omni Thread Library resources

Recommended Posts

I've heard about the Omni Thread Library for years, authored by Primož Gabrijelcic, but have never more than glanced at it. Most of the examples I've seen have been fairly basic, and I didn't have any reason to suspect it might be more than what you find in Delphi's PPL. Boy, I couldn't have been more wrong!

 

It took an amazing amount of digging around to find stuff, and after all of this I thought it would be helpful to memorialize my discoveries here for everybody to see. This is truly a remarkable piece of work.

 

The library itself is FOSS, and Primož has posted stuff in his blog about it over time -- which spans 10 years now! It's a LOT of material to go through.

 

You can get the latest source code (V3.07.9) on github here: https://github.com/gabr42/OmniThreadLibrary

 

The version in GetIt is a bit outdated (3.07.7)

 

Rather than publish a detailed Help guide or online resource, he chose to publish a book that can be purchased here: https://leanpub.com/omnithreadlibrary

 

NOTE: you can get the book + the 3 webinars described later for even less than it says below at the LeanPub link. 


For the record, I don't particularly like digging through source code trying to figure things out, so I started hunting around for other resources.

 

I found a recording (33 min) of CodeRage talk he gave at CodeRage 6 in 2011, and a free replay (MP4 download) of it can be found here: https://cc.embarcadero.com/item/28591

 

I watched that video and discovered that this is an UTTERLY AMAZING treasure trove of material, and it covers things I never imagined to see!

 

It actually has seven (7) high-level abstract APIs (if you will) that really surprised me to see -- I'd figured it was mostly just the first few that are found in most threading libraries.

 

* Async -- start a background task and continue    -- this is similar to, yet more than what Delphi's PPL does

* Future -- start background task, do some work, then wait to retrieve the result

* ParallelTask -- start multiple copies of one task (for background procesing) and then wait for them all to complete

* Join -- start several different background tasks and then wait for them to complete

* Fork/Join -- do a divide and conquer, in parallel

* ForEach -- perform a parallel iteration over an integer range, or on the contents of any container with built-in iterators (ie, that support for-in)

* Pipeline -- run a multi-stage process
 

I found a post here referring to an article  using Deplhi's TCountdownEvent that has a bunch of code that does what Fork/Join does in just a handful of lines of code.

 

What really blew my socks off were the ForEach and the Pipeline methods. 


I wanted more and kept digging.... eventually I found this:

 

https://thedelphigeek.gumroad.com

 

There you can find three hour-long tutorial videos on all of those high-level APIs. 

 

The link I found only went to the first video. I took a wild guess and found #3 that had the info on ForEach and Parallel, which is what I wanted, and bought that one. They're $10 each.

 

Later I found out you can get all three for $25. ** EDIT: you can get these with the book at the LeanPub link above for just $5 more.

 

NOTE: I'm mentioning this stuff here because it's so damn hard to find these things anywhere! You'll never find them searching for the term "video" because they're only referred to as "webinars". 

 

The couple of links I found on his blog specifically for these VIDEOS were dead and the ones in another place were wrong. I only found these because of one very brief and cryptic reply to a random question way down on one blog entry from 2015. I didn't notice them on LeanPub because I wasn't looking for "webinars".

 

I started this search because I've been going around in circles trying to figure out the best way to deal with a collection of data displayed in a TListView with some data objects attached to the ListItems' .Data property, and I want to allow the user to hit a "Go!" button and have the code process the whole lot in parallel. They need to issue a bunch of REST service calls, so mostly they're just sitting idle waiting for replies, and I suspect they can all complete in the time it takes for the longest one to run.

 

It turns out the ForEach<T> lets you iterate over the Listview's Items property using it's existing Iterator, and pass each TListItem into an anonymous Proc (by type!) and process it directly -- with each one running in parallel. You can easily do something when each of those threads finishes running, and also when the entire list is processed, all without having to send messages or go through crazy contortions to effect a fork/join operation. It's all handled invisibly. WHOA!
 

The Pipeline abstraction is really something to behold. Many years ago I spent 9 months building a system that implemented a multi-step pipeline process that literally was spread out over a half-dozen different PCs on a LAN. Today they could all be done on one multi-core CPU, and using this bit of code would let me build it in less than a week (assuming all of the component parts were already working). In fact, i've built a few other systems that could have benefitted from this abstraction as well. And I can think of one coming up that might be able to use it. 

 

In summary ... maybe a lot of folks discovered this library when it was first published in 2010, but it seems to have virtually disappeared aside from an occasional mention in comparison to Delphi's Parallel Processing Library. Personally, I don't even think they're comparable. If you've never looked at it and are doing parallel programming stuff, take a look. And if you're famliar with it but may not have remembered all it does, give it another look and see if there's anything new worth studying. 

 

BTW, one thing that it had been criticized about was no way to Cancel threads in several places; the latest version (3.07.9) that's on github fixes that. It's relatively new, and not in the GetIt version.

 

Edited by David Schwartz
  • Like 9
  • Thanks 4

Share this post


Link to post

I've been telling anyone who would listen for years that Omni is the best thing since sliced bread. We use it in the stepping engine in FinalBuilder. Some of the low level stuff is hard to grok though, I struggle with it when I haven't worked on it for a while, but the high level stuff is simple to use. 

  • Like 3
  • Thanks 1

Share this post


Link to post

oops ... seems I was a bit overoptimistic with something....

 

On 12/26/2021 at 3:24 AM, David Schwartz said:

It turns out the ForEach<T> lets you iterate over the Listview's Items property using it's existing Iterator, and pass each TListItem into an anonymous Proc (by type!) and process it directly -- with each one running in parallel. You can easily do something when each of those threads finishes running, and also when the entire list is processed, all without having to send messages or go through crazy contortions to effect a fork/join operation. It's all handled invisibly. WHOA!

 

I get a run-time error attempting to use iterators that return class types. The only examples I've found that work with collection classes, like TStringlist, all return basic types, like integers, strings, etc., not class instances.

 

That seems rather odd because objects are merely references passed as typed pointers.

 

Am I missing something here?

 

Share this post


Link to post

These fragments may help..

   Jobs: Tlist<TProc>;
   ....
   for var job: TProc in Jobs do
         job();  //TProc(job);      //parens help compiler and old VBers to know whats going on.
		 

	Using dictionary allows using components directly may be of use.
	
   //source Remu Lebeau:) https://stackoverflow.com/questions/64145742/cannot-change-tedit-text-in-delphi
  TtextPair = Tpair<Tcomponent, Tcomponent>;
....
  
    Edits: TList<TtextPair>;
	
	  tp: TtextPair;
begin
  for tp in Edits do
  begin
    Edit := TEdit(tp.Key);
    dLabel := TLabel(tp.Value);
    var
      I: NativeInt;
    for I := 1 to Length(Edit.text) do
      if ((Edit.text[I] = ' ') and (I <> Edit.tag)) then
      begin
        dLabel.Transparent := False;
        dLabel.Color := clYellow;
        dLabel.caption := 'gap at pos ' + I.tostring;
        Edit.tag := I;
        break;
      end

 

Share this post


Link to post
40 minutes ago, Pat Foley said:

These fragments may help..

Thanks, but it's not related. 

 

When I set up the Parallel.ForEach<TListItem>( aListView.Items ).Execute( procedure( const task: IOmniTask; const aLI : TListItem ) begin . . .

 

I get this run-time error from the core logic trying to deal with the items it's getting -- clearly, they're TListItem type.

 

First chance exception at $75CBB5B2. Exception class Exception with message 'TValue of type tkClass cannot be converted to TOmniValue'. Process ThreadingTest2VCL.exe (8516)

 

The exception is being thrown in procedure OtlCommon.TOmniValue.SetAsTValue(const value: TValue);

 

If I change it to a pointer (eg., Parallel.ForEach<pointer>), that fails as well, I'm guessing because the iterator (for TListView.Items) still returns an object that cannot be converted to a TOmniValue.

 

I'm not sure if that's a bug or "as designed". 


I've found examples that show use of a TList<integer> that returns 'integer' types, and ForEach<TStringlist> that returns 'string' types. I haven't found anything that returns class instances, just basic types.

Edited by David Schwartz

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×