Class NDScopedAsyncAttribute
- Namespace
- NumSharp
- Assembly
- NumSharp.dll
The ASYNC counterpart of NDScopedAttribute: marks an async method, an
async iterator, or a non-async method returning Task/ValueTask[<T>]
as an NDScope boundary. At build time the NumSharp IL weaver
(tools/NumSharp.Build, shipped to consumer projects as the NumSharp.Build
NuGet package) weaves the method's compiler STATE MACHINE — or, for a non-async
Task-returning body, its DEFERRAL egress — so the NDArray temporaries it
drops are reclaimed at the invocation's completion instead of waiting on the finalizer,
with the source keeping its 100% original body exactly as NDScopedAttribute
does for synchronous ones.
[AttributeUsage(AttributeTargets.Method|AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class NDScopedAsyncAttribute : Attribute
- Inheritance
-
NDScopedAsyncAttribute
- Inherited Members
Remarks
What this attribute covers (the shapes that suspend across await, or
defer disposal to a task's completion — everything that needs the async scope seam):
- Async methods —
Task/Task<T>/ValueTask/ValueTask<T>/void, and any custom[AsyncMethodBuilder]task-like. - Async iterators —
IAsyncEnumerable<T>(await+yield return). - Non-async methods returning
Task/ValueTask[<T>] — e.g.Task<NDArray> M() => ComputeAsync();.
How the weave works. The attributed method is a compiler-generated stub; the
real code — and every egress — lives in the state machine's MoveNext. The weaver
gives it ONE scope for the whole logical invocation (held in a weaver-added state-machine
field), UNINSTALLED before each await's continuation is scheduled and re-installed on
whatever thread resumes — so temps stay alive while an awaited callee still uses them, and
everything is reclaimed at SetResult/SetException (async), at the final MoveNext
(async iterators), with results and yield returned elements routed through the same
Returns/YieldTo egress. A NON-async method returning Task/ValueTask
yields a completed task's result immediately and DEFERS the scope's disposal to an
incomplete task's completion (the in-flight callee may still hold tracked temps); an
incomplete ValueTask is Preserve()d — the caller receives the multi-observable
form.
SYNCHRONOUS iterators stay on NDScopedAttribute. An
IEnumerable<T>/IEnumerator<T> (yield return without
await) also compiles to a state machine and uses the same invocation-scope seam, but
it is not asynchronous — it is woven by [NDScoped]. Marking a synchronous iterator
[NDScopedAsync] is a build ERROR (NDW010); marking an async method, async iterator,
or Task-returning method [NDScoped] is the mirror-image ERROR (NDW009). Each error
names the correct attribute — a method has exactly one scoping model, and choosing the
wrong attribute never silently ships an unwoven method.
The weaver REJECTS (build error NDW003) a shape whose egress it cannot see — an
UNSUPPORTED carrier (a bespoke reference type, a collection, or a result struct that does
NOT implement INDArrayCarrier) as the async RESULT or as the T inside a
Task<T>; scope those by hand (NDW004 = an unrecognized, non-C# state-machine
shape; NDW008 = the referenced NumSharp predates the async seam).
A method whose body already opens an NDScope is skipped (idempotence).
Inherited by overrides and implementations exactly like
NDScopedAttribute: on a virtual, abstract or interface member it is the contract
every override/implementation is woven under (an async override of a non-async
Task-returning declaration, or the reverse, each weave through their own shape — the
attribute names the model, the override supplies the body). An override's own
[NDScoped]/[NDScopedAsync]/[NDScopedCovered] wins over the inherited one;
a body-less attributed declaration is the contract, not an NDW005.
PUBLIC because the attribute is consumer-facing: a project that installs the
NumSharp.Build package marks its own async/Task composition methods with it. Without
the package the attribute is inert metadata — the method runs unscoped, the pre-weave
finalizer-backstop behaviour.