Table of Contents

Read a file asynchronously

Intermediate · C#. ParseAsync reads the bytes with ReadAsync while the thread is free and decodes them with the same reader as Parse; the token bounds the wait, and cancellation is an OperationCanceledException rather than a read failure.

Run this example

Prerequisites: the repository's .NET 10 SDK and a checkout of this source. Run from the repository root:

dotnet run --project docs/examples/CStructSharp.Docs.Examples.csproj -c Release -- async-stream

The runner checks a header read with ParseAsync from a file opened for asynchronous I/O, the payload that follows, a non-throwing read of three trailing bytes with the stream back at its origin, and a cancelled token leaving position 0. Success includes PASS async-stream.

This example uses the C# API. Browser capabilities and result shapes are described in the browser guide.

Complete program

The layout, options, input bytes, helper methods, and required types are all included. To adapt it outside the repository, create a .NET 10 console project, add CStructSharp, and replace Program.cs with this complete file. These examples follow the source version; use a matching package when testing a release.

Download the complete C# source.

// Generated from executable documentation examples. Edit the source region, then regenerate.
using System;
using System.IO;
using System.IO.Pipelines;
using System.Linq;
using System.Buffers;
using System.Collections.Generic;
using System.Dynamic;
using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using CStructSharp;
using CStructSharp.Codecs;
using CStructSharp.Diagnostics;
using CStructSharp.Introspection;
using CStructSharp.Values;

internal static partial class Program
{
    public static async Task Main()
    {
        await AsyncStream();
        Console.WriteLine("PASS async-stream");
    }

    /// <summary>Parses a file asynchronously and verifies failure, cancellation and stream positioning.</summary>
    private static async Task AsyncStream()
    {
        // A file holds a six-byte header and a payload. Opened for asynchronous I/O, the header is read with
        // ParseAsync: the bytes arrive through ReadAsync while the thread is free, then the ordinary reader decodes them.
        var layout = new CStruct("struct header { uint16 kind; uint32 length; };");
        string path = Path.Combine(Path.GetTempPath(), $"cstructsharp-{Guid.NewGuid():N}.bin");
        await File.WriteAllBytesAsync(path, [0x02, 0x00, 0x06, 0x00, 0x00, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]);
        try
        {
            await using var file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: true);

            // A token bounds the wait: a stalled disk or network ends in OperationCanceledException, not a read failure.
            using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10));
            StructValue header = await layout.ParseAsync(file, "header", cancellationToken: timeout.Token);
            Equal((ushort)2, header.Get<ushort>("kind"));
            Equal(6u, header.Get<uint>("length"));
            Equal(6L, file.Position);

            // The payload's length came from the header; the rest of the file is read the usual way.
            byte[] payload = new byte[header.Get<uint>("length")];
            await file.ReadExactlyAsync(payload, timeout.Token);
            Equal("AABBCCDDEEFF", Convert.ToHexString(payload));

            // Three bytes at the end are not a header: the non-throwing form reports the failure the throwing form
            // would raise, and a seekable stream is back where it started.
            file.Position = 9;
            ReadAttempt<StructValue> attempt = await layout.TryReadValueAsync<StructValue>(file, "header", cancellationToken: timeout.Token);
            True(!attempt.Succeeded, "three bytes are not a header");
            True(attempt.Failure is CStructReadException, "the failure is the read exception");
            Equal(9L, file.Position);

            // A cancelled token stops the read before any byte is taken.
            using var cancelled = new CancellationTokenSource();
            cancelled.Cancel();
            file.Position = 0;
            try
            {
                await layout.ParseAsync(file, "header", cancellationToken: cancelled.Token);
                True(false, "unreachable");
            }
            catch (OperationCanceledException)
            {
                Equal(0L, file.Position);
            }
        }
        finally
        {
            File.Delete(path);
        }
    }

    private static void Equal<T>(T expected, T actual)
    {
        if (!EqualityComparer<T>.Default.Equals(expected, actual))
        {
            throw new InvalidOperationException($"Expected '{expected}', received '{actual}'.");
        }
    }

    private static void True(bool condition, string message)
    {
        if (!condition)
        {
            throw new InvalidOperationException(message);
        }
    }
}

Try it and diagnose mistakes

Read the header with TryReadValueAsync from position 3 instead of 9.

Answer: The attempt fails the same way (three bytes of header and three of payload are not a header) and the position returns to 3. The program contains assertions for its original inputs. When changing an input intentionally, update the expected assertion too; an unchanged assertion is not evidence that the new value is wrong.

Continue with the related guide or choose another recipe.