On this page

Parallel invoice processing and an optional feature

The report worker calls IReportRenderer. Its implementation reads invoice snapshots, calculates each invoice independently, then publishes one customer summary. The input belongs to one customer and one currency, EUR. Every line amount and paid amount has at most two fractional digits. Overpayment means zero outstanding in this report; it is not a credit-ledger implementation.

The ordinary application contracts define InvoiceSnapshot, StatementRow, CustomerReport and IInvoiceSource. The source returns independent values and releases its reader before processing starts. It must supply each invoice once: duplicate snapshots would be counted twice, so a joined query must group its rows correctly. IReadOnlyList alone does not make its objects immutable: the source implementation must also stop modifying the snapshots and their line lists.

Read once, then project independently

This report module uses root_namespace DirectSharp.Showcase. The host provides the scoped source and two borrowed Gates shared across report jobs: two permits for database reads and eight for projection. The class implements the ordinary IReportRenderer CLR contract through a named implementation block.

import Global.System
import Global.System.Collections.Generic
import Global.System.Linq

public class CustomerReportRenderer
    private readonly source as IInvoiceSource
    private readonly database as Gate
    private readonly projection as Gate

    public constructor(invoiceSource as IInvoiceSource, databaseGate as Gate, projectionGate as Gate)
        require invoiceSource is not nothing
        require databaseGate is not nothing
        require projectionGate is not nothing
        source = invoiceSource
        database = databaseGate
        projection = projectionGate
    end constructor

    implements IReportRenderer
        public async action RenderAsync(customerId as Guid)
            returns CustomerReport
            let invoices as IReadOnlyList <of InvoiceSnapshot> = nothing
            enter database
                invoices = await source.ReadAsync(customerId, Cancellation.Token)
            leave
            if invoices.Count > 1000 then
                throw new InvalidOperationException("Report exceeds the 1000-invoice batch limit")
            end if

            concurrent for_each invoice in invoices collects rows as StatementRow with limit: 4
                enter projection
                    collect rows = await StatementMath.ProjectAsync(invoice, Cancellation.Token)
                leave
            end for_each
            return new CustomerReport(customerId, invoices.Count, rows.Sum(.Outstanding))
        end action
    end implements
end class

RenderAsync's explicit async contract supplies the compatible optional CancellationToken parameter of IReportRenderer and its Cancellation context. The gate around the read ends before projection starts. Holding a database permit while waiting for CPU work would waste scarce database capacity.

Each iteration has its own invoice and result. Collect preserves input order despite differing completion times. There is no shared mutable List to append to from workers. A malformed invoice fails the report; this example does not silently publish partial totals. Empty input produces a zero-count, zero-total report. Decimal overflow remains an error.

The source contract bounds a report to 1000 invoices and must enforce its own row/payload limits during acquisition. The renderer's count check is a defensive boundary, not a cure for an unbounded source materialization. A larger reporting product needs explicit paging, checkpointing and stable-snapshot policy. A live database reader must never be shared between concurrent iterations; copy each row's needed values first.

Make CPU scheduling explicit

Concurrent async code overlaps waits; it does not automatically put ordinary arithmetic on separate threads. StatementMath.ProjectAsync makes the scheduling choice through an ordinary .NET Task.Run call. This DirectSharp method belongs beside the application's Compute action:

public static async action ProjectAsync(invoice as InvoiceSnapshot)
    returns StatementRow
    let token = Cancellation.Token

    action Project()
        returns StatementRow
        return Compute(invoice, token)
    end action

    return await Global.System.Threading.Tasks.Task.Run <of StatementRow>(
        action Project, token
    )
end action

Compute loops through the invoice's line amounts, checks cancellation between items, validates the monetary inputs, sums with checked Decimal arithmetic and subtracts the paid amount. The complete reference implementation defines those operations. Passing a token to Task.Run alone cannot interrupt code already executing; the checks inside Compute provide cooperation.

The scheduling cost is worthwhile only for substantial CPU work. For a short invoice, call the synchronous Compute method directly. The explicit ProjectAsync method makes the tradeoff visible instead of making every asynchronous operation a background-thread dispatch. Its task is awaited inside the bounded iteration, so the permits remain held until that CPU operation settles.

Start with ordinary LINQ

For an export, use the projected rows rather than recalculating invoices. The following fragment assumes the collected rows and a caller-owned TextWriter. It filters zero balances, orders by unique invoice identifier and writes a small CSV:

let selected = rows.Where(.Outstanding > 0).OrderBy(.Id)
StatementRows.Write(selected, writer)

StatementRows.Write is an ordinary application method taking IEnumerable<StatementRow> and TextWriter. It writes InvoiceId and OutstandingEUR columns using Guid D format and invariant two-decimal formatting. The format has no free-text fields, so it does not pretend to be a general CSV escaping library. It borrows the writer and does not close it. Writer/enumeration failures propagate; a partial output is possible. Publish a temporary file only after successful completion when partial output must stay invisible.

Add a small feature for repeated export pipelines

If this shape repeats across many reports, the following feature exposes that composition. The business arithmetic still lives in StatementMath and ordinary application classes. The adapter uses the existing feature declaration mechanism and calls the same LINQ and writer methods:

feature statement_rows
    target Global.System.Collections.Generic.IEnumerable <of Global.DirectSharp.Showcase.StatementRow>

    operation where(predicate as captured expression)
        calls Global.System.Linq.Enumerable.Where
        sets current from result
    end operation

    operation sort_by(key as captured expression)
        calls Global.System.Linq.Enumerable.OrderBy
        sets current from result
    end operation

    operation export(writer as Global.System.IO.TextWriter)
        calls Global.DirectSharp.Showcase.StatementRows.Write
    end operation
end feature

Use this optional replacement for the preceding two statements:

statement_rows rows
    where .Outstanding > 0
    sort_by .Id
    export writer
end statement_rows

Where and sort_by replace the current sequence; export consumes it through the ordinary method's source argument and the supplied writer. The captured predicate/key bind to LINQ delegate types, not a separate rule engine. Filtering and sorting retain their normal deferred behavior until Write enumerates them. Sorting buffers its selected input; the feature does not make it streaming or parallel. Source and output errors are not caught automatically.

The feature adds vocabulary only within its own block. It is discovered from the application's source/reference declarations; it does not install a library, add a runtime object or change cancellation/disposal semantics. Use the ordinary calls when they are already clearer.

Where the other language tools fit

Application need Appropriate tool
Repeated validation header Validator class kind; normal FluentValidation rules
Independent dashboard workflows Inline run blocks and structured joining
Repeated independent input items Concurrent for_each and ordered collect
Work arriving over time ChannelReader with concurrent consume
Shared scarce capacity A borrowed Gate, owned and disposed by the host
Short compound state transition in the application registry Ordinary private synchronization; no awaited work inside its lock
Scoped services and asynchronous cleanup A per-job DI scope and using
Repeated pipeline composition Optional statement_rows feature over ordinary APIs
Live UI progress stream An ordinary observable/channel library if needed; no new event or observable language protocol

Patterns, reconciliation and import policies remain useful separate recipes. They are not forced into every report, and an import's activation policy does not silently change interactive customer registration.

Previous: queued reports. Continue to reference C# and verification cases.