Catalog · How the catalog works
CocinAsync
. Delphi High performance library for Delphi to simplify coding and improve performance of asynchronous and multithreaded applications.
Anyone may press Interesting, signed in or not. It helps others find this project.
README
CocinAsync
A high performance library for Delphi to simplify coding and improve performance of asynchronous and multithreaded applications.
Installation
Option A — DPM (recommended for application projects)
DPM (Delphi Package Manager) installs CocinAsync as a
source-only package. DPM downloads the sources into your local package cache and
adds them to the project search path via $(DPMSearch). Nothing is registered in the
IDE and no .bpl is required — units compile into your app like any other source.
| Package id | sivv.cocinasync |
| Feed | delphi.dev public gallery (registered by default on first DPM run) |
| Delphi versions | 10.0 Seattle through 13.0 Florence |
| Installed units | Core, VCL, FMX, console, and Flux units (under src\ and src\flux\ in the package cache) |
1. One-time setup
-
Install the DPM client (command-line tool and IDE plugin). Leave Add to PATH enabled.
-
Confirm a gallery source is configured:
dpm sources ListYou should see a source pointing at
https://delphi.dev/...(often namedDPMordelphi.dev). If not:dpm sources Add "-name=delphi.dev" "-source=https://delphi.dev/api/v2/index.json" -type=DPMServer dpm sources Enable "-name=delphi.dev"
2. Add the package to your project
From the IDE: open your .dproj, right-click the project → Manage DPM
Packages, search for sivv.cocinasync, and install.
From the command line (run from any folder):
dpm install sivv.cocinasync C:\path\to\YourProject.dprojPin a specific release:
dpm install sivv.cocinasync -version=1.0.0 C:\path\to\YourProject.dprojLimit platforms if needed (defaults to all platforms your project targets):
dpm install sivv.cocinasync -platforms=Win32,Win64 C:\path\to\YourProject.dproj3. Use it in code
uses
cocinasync.async,
cocinasync.jobs,
cocinasync.collections,
cocinasync.promise;
uses cocinasync.event.vcl; // optional: VCL event helpers
uses cocinasync.monitor.vcl; // optional: VCL job monitor form
uses cocinasync.event.fmx; // optional: FMX event helpers
uses cocinasync.monitor.fmx; // optional: FMX job monitor form
uses cocinasync.event.console; // optional: console event helpers
uses cocinasync.monitor.console; // optional: console job monitor
uses cocinasync.flux; // optional: Flux state management
uses cocinasync.flux.view.vcl; // optional: Flux VCL views
uses cocinasync.flux.view.fmx; // optional: Flux FMX viewsAdd only the units your project needs. Core units (cocinasync.async, cocinasync.jobs,
etc.) are framework-agnostic. VCL, FMX, console, and Flux units are optional layers on
top of the core library.
4. What DPM changes in your .dproj
After install, commit the DPM-related edits to version control:
PackageReferenceelements at the bottom of the.dproj(package id + version).$(DPMSearch)added toDCC_UnitSearchPathfor each installed platform.
Other developers (and CI) do not need a copy of this repo. After cloning your project:
dpm restore C:\path\to\YourProject.dprojThat downloads the referenced package version into %AppData%\.dpm\ and wires up the
same search paths.
5. Upgrade or remove
dpm install sivv.cocinasync -version=1.0.1 -upgrade C:\path\to\YourProject.dprojUse the IDE package manager to uninstall, or edit the PackageReference entries and
run dpm restore.
Local / private feed
If your team mirrors packages internally, register that folder or server as an
additional DPM source and pass -source=YourSourceName on install / restore. The
package spec lives at
sivv.cocinasync.dspec.yaml.
Option B — Manual (clone / submodule / search path)
Clone this repo (or add it as a submodule) and add the repo root and flux\ folder to
your project's search path. Build the .dpk packages in the IDE, or compile units
directly into your project. Demo and test projects in this repo use relative search
paths so contributors can build without DPM.
Maintainers — pack and publish
Tag the release first (git tag release/1.0.0), then:
$env:DPM_API_KEY = 'your-api-key'
.\scripts\publish-dpm.ps1The script reads the version from the release/* tag at HEAD (or the latest
release/* tag), packs into dist\dpm, and pushes to the delphi.dev gallery.
Pack only: .\scripts\publish-dpm.ps1 -PackOnly · Push only: -PushOnly · See
scripts/publish-dpm.ps1.
Units
Unit: cocinasync.async
Inline coding helpers for implementing asynchronous actions
This unit includes two synchronization helpers that make it easy to make sure code is executed in the main thread. They automatically protect you from accidentally synchronizing from the main thread.
#!delphi
TAsync.SynchronizeIfInThread(
procedure
begin
// Do something in the main thread
end
);
TAsync.QueueIfInThread(
procedure
begin
// Do something in the main thread
end
);Asynchronous operations can easily be performed inline in code. The global Async object may be used when lifetime of the async operations is not dependent upon another object (like a form or data module).
#!delphi
Async.AfterDo(1000,
procedure
begin
// Execute this code after waiting 1s
end
);
Async.DoLater(
procedure
begin
// Execute this code at some point in the very near future.
end
);
Async.OnDo(
function : boolean
begin
// Check for some condition, return true if the due should be executed. False if it should not
end,
procedure
begin
// Executes only when the condition above has been met
end,
1000, // interval on which the check function should be called
function : boolean
begin
// Return True to continue checking or False to abort
end
);
Aync.DoEvery(1000,
function : boolean
begin
// Execute this function every 1s. Return True to allow it to execute again or False to stop.
end
);When lifetime of an asynchronous operation depends upon another object such as a form or data module, create an instance of TAsync for that object. If the global Async is used, then it's possible the asynchronous methods will be called after needed objects are freed.
#!delphi
procedure FormCreate(Sender : TObject);
begin
FAsync := TAsync.Create;
FAsync.DoEvery(10,
procedure
begin
Shape1.Left := Random(Width-Shape1.Width);
end
);
end;
procedure FormDestroy(Sender : TObject);
begin
FAsync.Free;
end;Unit: cocinasync.collections
Lock-Free generic collection classes for high performance use in mutlithreaded applications
- TQueue<T> - high performance queue collection that is thread safe without locking
- TStack<T> - high performance stack collection that is thread safe without locking
- THash<T> - high performance hash collection that is thread safe without locking
cocinasync.global
Utility objects
cocinasync.jobs
A background job runner which easily executes tasks in the background
Delphi's System.Threading unit includes a Task execution library which on it's surface is very useful. It suffers from a throttling algorithm that can cause performance issues for some workloads. Jobs is an attempt to execute background tasks in a highly performant way through natural throttling based on workload and system capacity. By default a global Jobs runner is created with one running thread per logical processor on the system.
#!delphi
// Queue a background job that will execute as soon as an idle job runner notices it.
TJobManager.Excute(
procedure
begin
// Add something to the Jobs Queue and execute as soon as possible
end
);
// Jobs can also return values. Asking for the return value
// will cause the thread to wait until the value is available.
jobCount := TJobManager.Execute<Integer>(
function : Integer
begin
Result := 0;
for i := 1 to 30 do
begin
sleep(1000)
inc(Result);
end
end
);
// ... do some other work here in the main thread or in other jobs
Result := jobCount.Result; // will stop execution of this thread here until
// the jobCount job has returned it's result.
// NOTE: if you do this in the main thread,
// gui operations will block at this point until
// the job returns. Also, if using in the main thread
// do not do operations from within the job that execute
// through synchronization as it will cause a deadlock.
// Sometimes you want to do a bunch of jobs and wait for them all to complete.
// To do this you can use a TJobQueue
queue := TJobQueue.Create;
TJobManager.Execute(
procedure
begin
// do something important
end,
queue
);
TJobManager.Execute(
procedure
begin
// do something else here
end,
queue
);
queue.WaitForAll; // Execution will stop here until all of the jobs in the queue have completed.Depending upon workload, it may be desirable to create a separate jobs queue for a particular workload. To do so call CreateJobs() with the number of threads you wish to dedicate to running the jobs.
#!delphi
procedure FormCreate(Sender : TObject);
begin
// Create a Jobs Runner with 25 threads
// Note that there is a default job runner under a global variable "Jobs"
// which should suffice in most applications.
FJobs := TJobManager.CreateJobs(25);
end;
procedure Button1Click(Sender : TObject);
begin
FJobs.Queue(
procedure
begin
// Add some workload to the jobs runner
end
);
end;Contribution guidelines
- Fork and PR
- Fixes to the core library are welcome and encouraged
- Performance improvement PRs must be provable
- Additions to the library will be accepted if it furthers the project goal to simplify asynchronous and multithreaded development and to do so with a focus on high performance.
MIT License
See LICENSE.
Copyright 2017 by Bugfish Limited
