Codebelt

Cuemon.Extensions.Threading

Small Task extensions that make ConfigureAwait intent readable at the call site.

.NET 10.0 / .NET 9.0 MIT v10.5.3 26,455 downloads

Overview

Cuemon.Extensions.Threading adds four Task and Task<TResult> extension methods that replace ConfigureAwait(true) and ConfigureAwait(false) with named intent. Instead of carrying Boolean flags through asynchronous code, callers can express whether a continuation should return to the captured synchronization context or continue without it.

The package is deliberately small. It complements System.Threading with a narrower surface than Cuemon.Threading, focusing on await configuration for normal task-based code instead of introducing broader looping or coordination primitives.

Key APIs

ContinueWithCapturedContext(this Task task) wraps ConfigureAwait(true) for non-generic tasks so continuation policy is explicit when the caller should resume on the captured synchronization context.

ContinueWithCapturedContext<TResult>(this Task<TResult> task) does the same for result-bearing tasks and keeps the awaited TResult flow unchanged while still naming the continuation choice.

ContinueWithSuppressedContext(this Task task) wraps ConfigureAwait(false) for non-generic tasks, which is the package's direct way to mark library and background code that should not hop back to a captured context.

ContinueWithSuppressedContext<TResult>(this Task<TResult> task) applies the same suppressed-context policy to Task<TResult> so async pipelines can keep their result values while opting out of context capture.

Basic usage

using System.Collections.Generic;
using System.Threading.Tasks;
using Cuemon.Extensions.Threading.Tasks;
using Codebelt.Extensions.Xunit;
using Xunit;

namespace Contoso.Threading.Tests;

public class InvoicePipelineTest : Test
{
    public InvoicePipelineTest(ITestOutputHelper output) : base(output)
    {
    }

    [Fact]
    public async Task ShouldProcessInvoiceWithExplicitAwaitPolicy()
    {
        var steps = new List<string>();
        var sut = new InvoicePipeline(steps);

        var result = await sut.ProcessAsync(42).ContinueWithCapturedContext();

        TestOutput.WriteLine(result);
        TestOutput.WriteLines(steps);

        Assert.Equal("invoice-42:sent", result);
        Assert.Equal(new[] { "loaded", "composed", "sent" }, steps);
    }

    private sealed class InvoicePipeline
    {
        private readonly List<string> _steps;

        public InvoicePipeline(List<string> steps)
        {
            _steps = steps;
        }

        public async Task<string> ProcessAsync(int invoiceId)
        {
            var document = await LoadAsync(invoiceId).ContinueWithSuppressedContext();
            var envelope = await ComposeAsync(document).ContinueWithSuppressedContext();
            return await SendAsync(envelope).ContinueWithSuppressedContext();
        }

        private Task<string> LoadAsync(int invoiceId)
        {
            _steps.Add("loaded");
            return Task.FromResult($"invoice-{invoiceId}");
        }

        private Task<string> ComposeAsync(string document)
        {
            _steps.Add("composed");
            return Task.FromResult($"{document}:ready");
        }

        private Task<string> SendAsync(string envelope)
        {
            _steps.Add("sent");
            return Task.FromResult(envelope.Replace(":ready", ":sent"));
        }
    }
}

Use this pattern when a small async workflow should state its continuation policy where each await happens instead of repeating raw ConfigureAwait flags. It matters because the package keeps task-based code readable while preserving the same Task and Task<TResult> behavior that the framework already provides.

Installation

dotnet add package Cuemon.Extensions.Threading

Usage guidance

Use this package when your code already depends on Task-based async flows and you want explicit, named continuation choices at each await site. If raw Task.ConfigureAwait(...) calls are sufficient for your team or you need broader concurrent-loop primitives instead of await-configuration helpers, stay with the framework API or move to Cuemon.Threading.

Family packages