Short answer
Dyalog APL (19.0 and later) does not impose its own cap on parallel tasks. When you submit parallel work through the .NET interop layer, scheduling and concurrency limits come entirely from the .NET Task Parallel Library and its thread pool. There is no Dyalog-specific configuration option — documented or otherwise — to throttle task counts. That is the likely explanation for the contention and memory spikes you observed: the engine queues everything you give it and lets the CLR decide what runs when.
How the limits actually work
Two separate limits matter here, and conflating them causes confusion:
- Queued tasks: effectively unlimited. You can submit thousands of tasks; they simply wait in the TPL queue. No error is raised by Dyalog or the TPL when the queue grows.
- Concurrently executing tasks: bounded by the .NET thread pool. The pool starts with a minimum thread count (roughly the logical processor count) and injects additional threads slowly — on the order of one or two per second — when work remains queued. The default maximum worker-thread count is very high in modern .NET (historically 250 per processor on .NET Framework; effectively unbounded by default on .NET Core/.NET 5+).
So the answer to "does it cap to logical processors" is: not exactly. Throughput is initially near the core count, but the pool grows beyond it under sustained load. The memory spikes you saw are consistent with many queued tasks each holding APL workspace state, not with an engine-level throttle being hit.
Thread-pool exhaustion behavior
When queued work exceeds available threads, excess tasks just wait — there is no rejection, exception, or back-pressure signal back into APL. The practical failure modes are indirect: slow ramp-up (the pool's deliberate injection delay), memory growth from queued task state, and GC pressure. If tasks block on each other while holding pool threads, you can also hit a form of starvation where the pool grows to its maximum and throughput collapses.
What you can control
Since Dyalog exposes no setting, adjust the pool from the .NET side before launching the workload. A small helper assembly works:
// C# helper, loaded via Dyalog .NET interop
public static class PoolConfig {
public static bool SetLimits(int maxWorker, int maxIo) {
return System.Threading.ThreadPool.SetMaxThreads(maxWorker, maxIo);
}
public static void GetLimits(out int maxWorker, out int maxIo) {
System.Threading.ThreadPool.GetMaxThreads(out maxWorker, out maxIo);
}
}
Call SetLimits once at session start, before any parallel work is queued. Note that SetMaxThreads cannot go below the processor count, and it returns false on failure — check the return value.
Often the better fix is application-level throttling: use a SemaphoreSlim or ParallelOptions.MaxDegreeOfParallelism in the helper so only N APL callbacks run concurrently, regardless of pool size. This bounds memory per-task deterministically, which pool tuning alone does not.
Verification steps
- Read the current limits via
ThreadPool.GetMaxThreads through interop and record them. - Run a script that spawns a known large number of parallel tasks and watch the process thread count in Task Manager or
Process.GetCurrentProcess().Threads.Count. - Apply
SetMaxThreads (or a semaphore-based limiter), re-run, and confirm the thread ceiling and peak memory both change. - Monitor GC collections and working set during the run to confirm contention is actually reduced.
Assumptions and uncertainty
This behavior depends on which CLR your Dyalog installation hosts (.NET Framework vs .NET Core/.NET 5+); pool defaults and injection rates differ between them. The exact interop mechanics of Dyalog 19.0's TPL submission are not fully documented publicly, so treat the helper-assembly approach as the reliable lever and verify on your specific version. If you can share which runtime Dyalog is loading (System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription), the pool-tuning advice can be made more precise.