Skip to content

MtClient/MtApi5: async command API with request pipelining#310

Open
biohazardxxx wants to merge 4 commits into
vdemydiuk:devfrom
biohazardxxx:feat/mtclient-async-commands
Open

MtClient/MtApi5: async command API with request pipelining#310
biohazardxxx wants to merge 4 commits into
vdemydiuk:devfrom
biohazardxxx:feat/mtclient-async-commands

Conversation

@biohazardxxx

Copy link
Copy Markdown

Summary

The RPC core already supports multiple in-flight commands (responses are correlated by commandId, and the expert drains its whole command queue per wake-up), but the public API is strictly synchronous — one blocked thread per outstanding command. This PR adds a first-class async path so callers can pipeline requests:

  • MtClient: CommandTask gains a TaskCompletionSource (created with RunContinuationsAsynchronously — continuations never run inline on the receive thread); new Task<string?> SendCommandAsync(...) mirrors SendCommand's registration, locking, and timeout semantics exactly. Purely additive — MT4's MtApiClient compiles untouched (verified).
  • MtApi5: private SendCommandAsync<T> mirrors SendCommand<T> exception semantics; public async variants for the read-heavy hot set: PositionsTotalAsync, OrdersTotalAsync, AccountInfoDouble/IntegerAsync, SymbolInfoTickAsync, SymbolInfoDouble/IntegerAsync, CopyRatesAsync (3 overloads, returning Task<MqlRates[]> directly instead of out params), CopyTicksAsync, TimeCurrentAsync.
  • Test client: AsyncPipelineDemo action comparing 20 sequential sync calls vs 20 concurrent async calls.

No async variants for trade operations by design — OrderSendAsync already exists as a synchronous wrapper of MQL5's OrderSendAsync command and a Task-returning overload of that name would be a foot-gun.

Implementation notes

  • Timeout semantics mirror sync exactly, including the benign race where a response arriving between timeout expiry and lock acquisition is still returned.
  • All library awaits use ConfigureAwait(false); no async void in library code.
  • CopyRatesAsync returns [] (never null) on a null response, matching the observable behavior of the sync overloads (review finding, fixed in c236352).

Testing

  • All four projects build clean (MtClient, MtApi5, MtApi — proving the MT4 surface untouched — and the test client).
  • Adversarial review traced deadlock/lock-ordering, task-leak, unobserved-exception, and semantics-parity concerns — no confirmed defects beyond the CopyRatesAsync null/empty drift, which is fixed.
  • Runtime-verified against a live MT5 terminal (build 5830):
    • Correctness: PositionsTotalAsync/TimeCurrentAsync results equal their sync counterparts.
    • Pipelining: 20 concurrent async commands complete in 102 ms vs 1997 ms for 20 sequential sync calls against an expert on a 100 ms polling timer (x19.6) — all 20 served in a single wake-up, as designed. Against a nudge-responsive expert: 5 ms vs 27 ms (x5.4).

🤖 Generated with Claude Code

biohazardxxx and others added 4 commits July 6, 2026 12:53
Add SendCommandAsync alongside the sync SendCommand. CommandTask now
completes a TaskCompletionSource (created with
RunContinuationsAsynchronously so awaiter continuations never run
inline on the receive thread) in addition to the existing
EventWaitHandle. Timeout semantics mirror the sync path: on timeout
the current response value (null if none arrived) is returned and the
task entry is removed. No existing signature or behavior changed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a private SendCommandAsync<T> mirroring SendCommand<T> (same
response parsing and ExecutionException semantics) and public async
variants: PositionsTotalAsync, OrdersTotalAsync, AccountInfoDoubleAsync,
AccountInfoIntegerAsync, SymbolInfoTickAsync, SymbolInfoDoubleAsync,
SymbolInfoIntegerAsync, CopyRatesAsync (x3 overloads returning the
array directly instead of out params), CopyTicksAsync, TimeCurrentAsync.

Multiple in-flight commands are pipelined and typically served in a
single expert wake-up. Trade operations are intentionally excluded:
OrderSendAsync already exists as a sync wrapper of MQL's
OrderSendAsync command and must not be shadowed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New button on the Time and Date tab runs 20 sequential sync calls
(PositionsTotal/TimeCurrent alternating) followed by the same 20 calls
concurrently via Task.WhenAll on the async variants, and logs both
wall-clock times to demonstrate command pipelining.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Matches the observable behavior of the sync CopyRates overloads
(ratesArray is never null) and the CopyTicksAsync/CopyTicks parity.
Review finding.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
hosseinkhojany pushed a commit to hosseinkhojany/mtapi that referenced this pull request Jul 22, 2026
…equest pipelining

# Conflicts:
#	TestClients/MtApi5TestClient/MainWindow.xaml
#	TestClients/MtApi5TestClient/ViewModel.cs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant