CanBusService Class¶
Default ICanBusService: attaches once to ICanBus.FrameObserved and fans each observed CanFrameView out to every registered Subscription (arc42 §5.3 "Multi-Protokoll-Demux", ADR-5), and implements SendConfirmedAsync(CanFrame, Nullable<TimeSpan>, CancellationToken) (arc42 §6.3, ADR-7; FR-RAW-030..034) on the same single ICanBus.FrameObserved subscription.
Inheritance Object → CanBusService
Implements ICanBusService, IDisposable
Constructors¶
CanBusService(ICanBus) Constructor¶
Creates a service that demultiplexes bus. Attaches to the bus's ICanBus.FrameObserved event immediately.
Parameters¶
bus ICanBus
Fields¶
CanBusService.DefaultBufferCapacity Field¶
Default per-subscription bounded buffer capacity when none is specified.
Field Value¶
CanBusService.DefaultConfirmTimeout Field¶
Default SendConfirmedAsync(CanFrame, Nullable<TimeSpan>, CancellationToken) echo-wait timeout when none is specified (FR-RAW-034).
Field Value¶
Properties¶
CanBusService.Bus Property¶
The underlying bus this service demultiplexes.
Implements Bus
Property Value¶
CanBusService.SubscriptionCount Property¶
Number of currently registered (not yet disposed) subscriptions. Primarily for diagnostics/tests: after disposing every subscription it returns to zero, proving no registry entries leak (FR-RAW-012).
Implements SubscriptionCount
Property Value¶
Methods¶
CanBusService.Dispose() Method¶
Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
Implements Dispose()
CanBusService.FindOverlappingFilterSubscriptions() Method¶
Diagnostic: finds every pair of currently registered, still-undisposed CanIdFilter-based subscriptions whose ID spaces overlap, and the range of CAN IDs each pair shares (FR-RAW-041, "Should") -- helps catch misconfiguration when multiple protocol instances were meant to have disjoint ID ranges but don't. Subscriptions registered via the generic Subscribe(Func<CanFrameEvent,bool>, Nullable<int>, bool) predicate overload are opaque and are not analyzable, so they are skipped.
public System.Collections.Generic.IReadOnlyList<CanKit.Pro.RawCan.FilterOverlap> FindOverlappingFilterSubscriptions();
Implements FindOverlappingFilterSubscriptions()
Returns¶
IReadOnlyList<FilterOverlap>
One FilterOverlap per overlapping pair, each naming the two subscriptions
and the ID range on which they collide. Empty when no two filters share ID space.
CanBusService.SendConfirmedAsync(CanFrame, Nullable<TimeSpan>, CancellationToken) Method¶
Sends frame and asynchronously confirms it was actually sent, using
a uniform abstraction regardless of whether the underlying bus has hardware TX echo
enabled (arc42 §6.3, ADR-7; FR-RAW-030). When the bus both declares
Echo and has WorkMode == ChannelWorkMode.Echo configured,
confirmation comes from an actually-matched echo frame (FR-RAW-031: byte-identical sends
pending at the same time are matched FIFO, one echo each, never to each other's — no
cross-matching or crash). The matching is by content, so an echo of a send that has
already timed out or been cancelled cannot be told from the next identical send's and
is matched to that send (#349);
otherwise it is a documented approximation based on driver acceptance
(IsApproximated, FR-RAW-032) -- that is, on the bus's
transmit returning a positive count, which means what that bus makes it mean: on a bus
wrapped in CanKit's QueuedCanBus it means queued in process, not taken by a
driver. Never hangs: timeout,
bus-off, and outright rejection all resolve the returned task within bounded time
(FR-RAW-033) — see TxConfirmation for exactly how.
Which adapters can take the echo path. It needs the adapter to hand the sent frame back flagged (IsEcho). Measured against CanKit 0.5.6: PCAN in Echo mode delivers its own frames unflagged and Kvaser delivers none on a quiet bus, so on both every confirmation times out although the frame was on the wire; Vector is not measured and is read from its source as not delivering one in Echo mode either. Use Normal with those adapters: the approximated confirmation is what they support. See docs/reviews/2026-10-06-echo-hardware-measurements.md.
public System.Threading.Tasks.Task<CanKit.Pro.RawCan.TxConfirmation> SendConfirmedAsync(CanKit.Abstractions.API.Can.Definitions.CanFrame frame, System.Nullable<System.TimeSpan> timeout=null, System.Threading.CancellationToken cancellationToken=default(System.Threading.CancellationToken));
Parameters¶
frame CanFrame
The frame to send. As with ICanBus.Transmit(CanFrame@), the caller remains the owner (TX-lease) and is responsible for disposing it after this call returns/completes — ICanBusService never disposes it. A CAN-FD payload whose length is not one a CAN-FD frame can carry (0–8, 12, 16, 20, 24, 32, 48 or 64 bytes) is not refused here. Its DLC rounds up to the next of those lengths, so on a real bus it goes out padded to that length. On the echo path the confirmation is matched by content, so on an adapter that echoes the frame as it went on the wire -- padded -- the echo is never matched and the call ends as Timeout although the frame was sent; CanKit 0.5.6's Vector CAN-FD transceiver builds a received frame's length from its DLC that way. Pass a canonical length to avoid it. Refusing a non-canonical length is a behaviour change, kept for the next major version (#352, #299).
Maximum time to wait before failing with Timeout (FR-RAW-034); null uses DefaultConfirmTimeout. On the echo path it bounds the wait for the echo; on the approximated path it bounds the wait for the driver to accept the frame (ICanBus.TransmitAsync(CanFrame, CancellationToken)), which every current adapter completes immediately, so it only bites for a bus whose asynchronous transmit stalls. A frame reported as timed out may still reach the wire later (the bus is asked to cancel the transmit, but a driver that ignores that cannot be stopped). Must be positive; a non-positive timeout throws ArgumentOutOfRangeException before anything is handed to the driver. A timeout above Int32.MaxValue milliseconds (about 24.8 days, the longest delay the runtime's timer measures on every target) is clamped to that bound, so TimeSpan.MaxValue waits about 24.8 days.
cancellationToken CancellationToken
Caller-supplied cancellation; cancels the returned task per standard .NET convention, distinct from the domain-level Timeout outcome. It is consulted immediately before the driver call — on the echo path inside the send lock, after any wait for another send's driver call — so a token cancelled up to that point abandons the send with OperationCanceledException and nothing reaches the driver (#294). A frame the driver has been handed goes out; the CanKit 0.5.6 adapters ignore the token inside their transmit. Disposing the service cancels an outstanding call the same way, on either path -- while it waits for the echo, or while an asynchronous ICanBus.TransmitAsync(CanFrame, CancellationToken) is still pending -- and it ends in OperationCanceledException (#352).
Implements SendConfirmedAsync(CanFrame, Nullable<TimeSpan>, CancellationToken)
Returns¶
Exceptions¶
ObjectDisposedException
The service is disposed, or is disposed while the call is still on its way to the
driver. On either path the disposed check and the hand-off to the driver are one step
with respect to IDisposable.Dispose: once Dispose has returned, no
send's frame is handed to the driver any more, and a send that loses that race fails
with this exception -- on the approximated path since #430, as the echo path always
did. A send that won it has handed its frame over -- Dispose does not return while
such a send is still inside the driver call -- and if that transmit is still pending,
Dispose cancels the call as described under cancellationToken.
Sends do not wait for each other's driver call on the approximated path; only
Dispose does. The exception is a Dispose called on a thread that is
inside a driver call of any CanBusService -- this one or another, on
either path (a driver disposing on a fault): it waits for no send, since sends queued
behind that call -- behind a lock the driver shares between services, say -- may
depend on it. In that case the guarantee above does not hold: a send already past the
check may still reach the driver after Dispose has returned.
CanBusService.Subscribe(CanIdFilter, Nullable<int>, bool) Method¶
Registers a subscription using the allocation-free ID-range/mask fast path (FR-RAW-010/013).
public CanKit.Pro.RawCan.ISubscription Subscribe(CanKit.Pro.RawCan.CanIdFilter filter, System.Nullable<int> bufferCapacity=null, bool includeEcho=false);
Parameters¶
filter CanIdFilter
ID-range or acceptance-code/mask filter. A default-initialized
struct (default(CanIdFilter), or a field never assigned) is accepted and matches
the standard ID 0 only -- see CanIdFilter; the predicate overload with a
null predicate is the one that accepts every frame.
bufferCapacity Nullable<Int32>
Bounded buffer capacity for this subscription; null uses DefaultBufferCapacity.
includeEcho Boolean
Whether this subscription also receives the local host's own transmit echoes; false by default. See the predicate overload above.
Implements Subscribe(CanIdFilter, Nullable<int>, bool)
Returns¶
CanBusService.Subscribe(Func<CanFrameEvent,bool>, Nullable<int>, bool) Method¶
Registers a subscription that receives every frame for which predicate returns true; a null predicate accepts all frames (FR-RAW-010).
public CanKit.Pro.RawCan.ISubscription Subscribe(System.Func<CanKit.Pro.RawCan.CanFrameEvent,bool>? predicate=null, System.Nullable<int> bufferCapacity=null, bool includeEcho=false);
Parameters¶
predicate Func<CanFrameEvent,Boolean>
Per-frame filter, or null to accept all frames. Runs on the bus's dispatch thread before the payload is copied, so the Frame it inspects aliases the adapter's RX lease and must not be retained beyond the call. It is never offered an echo unless includeEcho is set.
bufferCapacity Nullable<Int32>
Bounded buffer capacity for this subscription; null uses DefaultBufferCapacity. When the buffer is full the oldest buffered frame is dropped so dispatch never blocks (FR-RAW-011).
includeEcho Boolean
Whether this subscription also receives the local host's own transmit echoes. Defaults
to false: a protocol layer that sees its own transmissions come back as if they
were peer traffic misbehaves in ways that are tedious to diagnose, so opting in is a
decision the caller makes deliberately (see the remarks below).
Implements Subscribe(Func<CanFrameEvent,bool>, Nullable<int>, bool)
Returns¶
Events¶
CanBusService.BackgroundExceptionOccurred Event¶
Raised when a caller-supplied subscription filter predicate throws during dispatch (FR-RAW-023-style fault channel). The failing frame is isolated to that subscription (delivery to the other subscriptions continues), and the exception is surfaced here instead of being silently swallowed. For a predicate fault it is invoked synchronously on the bus's dispatch thread, so handlers must return quickly and must not call back into the service.
Also raised for a failure of the callback, or of the delivery pump, of a
Subscribe(this ICanBusService, Action<CanFrameEvent>, Func<CanFrameEvent,bool>, Nullable<int>, bool, Action<Exception>)
subscription given no onError. That one is invoked on the subscription's
delivery task, a thread-pool thread, not on the dispatch thread.
Also raised, with the adapter's own exception, whenever the bus reports a fault through
ICanBus.FaultOccurred. Under the default CanExceptionPolicy (a lower
FaultThreshold or a custom Classifier can make any reported error one) the
only fault a CanKit 0.5.6 adapter raises is its receive loop dying, after which no
further frame arrives on this bus: outstanding
SendConfirmedAsync(CanFrame, Nullable<TimeSpan>, CancellationToken) calls end as Timeout
and Frames streams stay open until disposed. A consumer that
only reads subscriptions learns of it here, not from its stream: an
ISubscription carries no fault signal of its own, so such a consumer keeps
the service it subscribed on and handles this event (#348). Invoked synchronously on
whichever thread the adapter raises its fault on.
Implements BackgroundExceptionOccurred