Async and cancellation
Every I/O-bound entry point has an async overload. The CPU-bound parse and write paths are sync — the async overloads exist purely to avoid blocking on stream reads / writes.
Parse
// From a stream
using var fs = File.OpenRead("song.lrc");
var result = await LrcParser.ParseAsync(fs, cancellationToken: ct);
// From a file path (async file open)
var result = await LrcParser.ParseFileAsync("song.lrc", cancellationToken: ct);
// From a TextReader
using var reader = new StringReader(source);
var result = await LrcParser.ParseAsync(reader, cancellationToken: ct);
ParseAsync(Stream) takes a fast path when the input is a MemoryStream whose
backing buffer is exposed (TryGetBuffer); otherwise it copies through an
internal MemoryStream then parses synchronously over the resulting span.
Write
// To a stream — UTF-8 takes a zero-intermediate-string fast path
using var fs = File.Create("song.lrc");
await LrcWriter.WriteAsync(doc, fs, cancellationToken: ct);
// Atomic file write (temp + rename; cleanup runs only on failure)
await LrcWriter.WriteFileAsync(doc, "song.lrc", cancellationToken: ct);
// To a TextWriter
using var sw = new StringWriter();
await LrcWriter.WriteAsync(doc, sw, cancellationToken: ct);
Cancellation contract
- The token is checked with
ThrowIfCancellationRequested()before any I/O begins. Stream.CopyToAsyncandStream.WriteAsynchonour the token.TextReader.ReadToEndAsync(CancellationToken)honours the token.- The synchronous parse/render loop after the I/O finishes does not poll the token — once bytes are in memory, the parse completes (typical files: sub-millisecond).
Pre-cancelled token
If the token is already cancelled when the call is made, the method throws
OperationCanceledException immediately without doing any I/O.
using var cts = new CancellationTokenSource();
await cts.CancelAsync();
await Should.ThrowAsync<OperationCanceledException>(
() => LrcParser.ParseAsync(stream, cancellationToken: cts.Token).AsTask());
Exception model
| Surface | Wraps in LrcParseException? |
|---|---|
| Strict-mode error diagnostic | ✅ — FirstError and PartialResult populated |
| Encoding could not be detected | ✅ — message-only |
IOException, FileNotFoundException, UnauthorizedAccessException |
❌ — propagated raw |
OperationCanceledException |
❌ — propagated raw |
ParseFile / ParseFileAsync annotate any LrcParseException they catch with
FilePath so you know which file failed.