From 497b9c6c3e05d100955dd3653714367288aa0164 Mon Sep 17 00:00:00 2001
From: Volodymyr Dombrovskyi <5788605+dombrovsky@users.noreply.github.com>
Date: Sun, 16 Aug 2026 17:08:23 -0600
Subject: [PATCH] build: automate release publishing and public API promotion
---
.github/workflows/publish.yml | 195 +++++++++++++++---
Directory.Build.targets | 5 +
.../PublicAPI.Shipped.txt | 22 ++
.../PublicAPI.Unshipped.txt | 1 +
.../PublicAPI.Shipped.txt | 1 +
.../PublicAPI.Unshipped.txt | 17 ++
.../PublicAPI.Shipped.txt | 3 +
.../PublicAPI.Unshipped.txt | 3 +
.../TaskFlow.Extensions.Time.csproj | 1 +
TaskFlow/DedicatedThreadTaskFlow.cs | 37 +++-
.../ExceptionTaskSchedulerExtensions.cs | 66 +++++-
.../ThrottlingTaskSchedulerExtensions.cs | 4 +
TaskFlow/PublicAPI.Shipped.txt | 144 +++++++++++++
TaskFlow/PublicAPI.Unshipped.txt | 57 +++++
TaskFlow/TaskSchedulerEnqueueExtensions.cs | 5 +
build/Get-ReleaseArtifacts.ps1 | 97 +++++++++
build/Promote-PublicApi.ps1 | 108 ++++++++++
build/tests/Test-ReleaseAutomation.ps1 | 176 ++++++++++++++++
18 files changed, 900 insertions(+), 42 deletions(-)
create mode 100644 Directory.Build.targets
create mode 100644 TaskFlow.Extensions.Microsoft.DependencyInjection/PublicAPI.Shipped.txt
create mode 100644 TaskFlow.Extensions.Microsoft.DependencyInjection/PublicAPI.Unshipped.txt
create mode 100644 TaskFlow.Extensions.Microsoft.Logging/PublicAPI.Shipped.txt
create mode 100644 TaskFlow.Extensions.Microsoft.Logging/PublicAPI.Unshipped.txt
create mode 100644 TaskFlow.Extensions.Time/PublicAPI.Shipped.txt
create mode 100644 TaskFlow.Extensions.Time/PublicAPI.Unshipped.txt
create mode 100644 TaskFlow/PublicAPI.Shipped.txt
create mode 100644 TaskFlow/PublicAPI.Unshipped.txt
create mode 100644 build/Get-ReleaseArtifacts.ps1
create mode 100644 build/Promote-PublicApi.ps1
create mode 100644 build/tests/Test-ReleaseAutomation.ps1
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index de81a77..30ee41d 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -1,33 +1,174 @@
-# This workflow will build a .NET project
-# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
-
name: Publish
-on: [workflow_dispatch]
+on:
+ workflow_dispatch:
-jobs:
- build:
+permissions:
+ # Settings > Actions > General must allow GitHub Actions to create pull requests.
+ contents: write
+ pull-requests: write
- runs-on: ubuntu-latest
+concurrency:
+ group: publish
+ cancel-in-progress: false
+jobs:
+ publish:
+ runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
- - name: Setup .NET
- id: setup-dotnet
- uses: actions/setup-dotnet@v5
- with:
- dotnet-version: |
- 8.0.x
- 10.0.x
- - name: Select .NET 10 SDK
- run: dotnet new globaljson --sdk-version "${{ steps.setup-dotnet.outputs.dotnet-version }}" --roll-forward latestPatch --force
- - name: Show .NET SDK
- run: dotnet --version
- - name: Restore dependencies
- run: dotnet restore
- - name: Build
- run: dotnet build --configuration Release --no-restore
- - name: Test
- run: dotnet test --configuration Release --no-build --verbosity normal
- - name: Publish
- run: dotnet nuget push "**/bin/Release/*.nupkg" --source "https://api.nuget.org/v3/index.json" --api-key "${{ secrets.NUGET_API_KEY }}"
+ - name: Require the default branch
+ shell: bash
+ run: |
+ if [[ "${GITHUB_REF_NAME}" != "${{ github.event.repository.default_branch }}" ]]; then
+ echo "Releases must run from ${{ github.event.repository.default_branch }}, not ${GITHUB_REF_NAME}." >&2
+ exit 1
+ fi
+
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+
+ - name: Setup .NET
+ id: setup-dotnet
+ uses: actions/setup-dotnet@v5
+ with:
+ dotnet-version: |
+ 8.0.x
+ 10.0.x
+
+ - name: Select .NET 10 SDK
+ run: dotnet new globaljson --sdk-version "${{ steps.setup-dotnet.outputs.dotnet-version }}" --roll-forward latestPatch --force
+
+ - name: Show .NET SDK
+ run: dotnet --version
+
+ - name: Restore dependencies
+ run: dotnet restore
+
+ - name: Build
+ run: dotnet build --configuration Release --no-restore -p:PackageOutputPath="$GITHUB_WORKSPACE/artifacts/packages"
+
+ - name: Test
+ run: dotnet test --configuration Release --no-build --verbosity normal
+
+ - name: Test release automation
+ shell: pwsh
+ run: ./build/tests/Test-ReleaseAutomation.ps1
+
+ - name: Validate release artifacts
+ id: package
+ shell: pwsh
+ run: >-
+ ./build/Get-ReleaseArtifacts.ps1
+ -SearchRoot artifacts/packages
+ -StagingDirectory "artifacts/release-${{ github.run_id }}"
+ -GitHubOutput $env:GITHUB_OUTPUT
+
+ - name: Validate release metadata
+ shell: bash
+ env:
+ VERSION: ${{ steps.package.outputs.version }}
+ PRERELEASE: ${{ steps.package.outputs.prerelease }}
+ run: |
+ if [[ -z "$VERSION" ]]; then
+ echo "Release version output is empty." >&2
+ exit 1
+ fi
+
+ if [[ "$PRERELEASE" == "true" && "$VERSION" != *-* ]]; then
+ echo "Version $VERSION is marked prerelease but has no prerelease suffix." >&2
+ exit 1
+ fi
+
+ if [[ "$PRERELEASE" != "true" && "$VERSION" == *-* ]]; then
+ echo "Version $VERSION has a prerelease suffix but is not marked prerelease." >&2
+ exit 1
+ fi
+
+ echo "Validated release tag/version metadata for $VERSION."
+
+ - name: Publish packages
+ shell: bash
+ run: |
+ for package in "${{ steps.package.outputs.directory }}"/*.nupkg "${{ steps.package.outputs.directory }}"/*.snupkg; do
+ dotnet nuget push "$package" \
+ --source "https://api.nuget.org/v3/index.json" \
+ --api-key "${{ secrets.NUGET_API_KEY }}" \
+ --skip-duplicate
+ done
+
+ - name: Create or verify GitHub release
+ shell: bash
+ env:
+ GH_TOKEN: ${{ github.token }}
+ VERSION: ${{ steps.package.outputs.version }}
+ PRERELEASE: ${{ steps.package.outputs.prerelease }}
+ ARTIFACT_DIRECTORY: ${{ steps.package.outputs.directory }}
+ run: |
+ if gh release view "$VERSION" >/dev/null 2>&1; then
+ git fetch --force origin "refs/tags/$VERSION:refs/tags/$VERSION"
+ target=$(git rev-list -n 1 "$VERSION")
+ if [[ "$target" != "$GITHUB_SHA" ]]; then
+ echo "Release $VERSION already exists for $target, not $GITHUB_SHA." >&2
+ exit 1
+ fi
+ gh release upload "$VERSION" "$ARTIFACT_DIRECTORY"/* --clobber
+ else
+ options=(--target "$GITHUB_SHA" --title "$VERSION" --generate-notes)
+ if [[ "$PRERELEASE" == "true" ]]; then
+ options+=(--prerelease)
+ fi
+ gh release create "$VERSION" "$ARTIFACT_DIRECTORY"/* "${options[@]}"
+ fi
+
+ - name: Promote public API baselines
+ if: steps.package.outputs.prerelease != 'true'
+ shell: pwsh
+ run: ./build/Promote-PublicApi.ps1
+
+ # Pull requests opened by GITHUB_TOKEN do not start another workflow run,
+ # so validate the exact promoted tree before committing it.
+ - name: Validate promoted baselines
+ if: steps.package.outputs.prerelease != 'true'
+ run: dotnet build --configuration Release --no-restore
+
+ - name: Open public API promotion pull request
+ if: steps.package.outputs.prerelease != 'true'
+ shell: bash
+ env:
+ GH_TOKEN: ${{ github.token }}
+ VERSION: ${{ steps.package.outputs.version }}
+ DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
+ run: |
+ branch="automation/public-api-$VERSION"
+ if git diff --quiet -- '*/PublicAPI.Shipped.txt' '*/PublicAPI.Unshipped.txt'; then
+ echo "Public API baselines are already promoted."
+ exit 0
+ fi
+
+ existing=$(gh pr list --head "$branch" --state open --json url --jq '.[0].url // empty')
+ if [[ -n "$existing" ]]; then
+ echo "Promotion pull request already exists: $existing"
+ exit 0
+ fi
+
+ if git ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1; then
+ echo "Remote branch $branch already exists without an open pull request." >&2
+ exit 1
+ fi
+
+ git config user.name "github-actions[bot]"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+ git switch -c "$branch"
+ git add -- '*/PublicAPI.Shipped.txt' '*/PublicAPI.Unshipped.txt'
+ git commit -m "Mark public APIs shipped in $VERSION"
+ git push origin "HEAD:refs/heads/$branch"
+ gh pr create \
+ --base "$DEFAULT_BRANCH" \
+ --head "$branch" \
+ --title "Mark public APIs shipped in $VERSION" \
+ --body "Promotes the public API baselines after the [$VERSION release]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/tag/$VERSION)."
+
+ - name: Skip baseline promotion for prerelease
+ if: steps.package.outputs.prerelease == 'true'
+ run: echo "Prerelease detected; skipping public API baseline promotion."
diff --git a/Directory.Build.targets b/Directory.Build.targets
new file mode 100644
index 0000000..07a1935
--- /dev/null
+++ b/Directory.Build.targets
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/TaskFlow.Extensions.Microsoft.DependencyInjection/PublicAPI.Shipped.txt b/TaskFlow.Extensions.Microsoft.DependencyInjection/PublicAPI.Shipped.txt
new file mode 100644
index 0000000..8dd3005
--- /dev/null
+++ b/TaskFlow.Extensions.Microsoft.DependencyInjection/PublicAPI.Shipped.txt
@@ -0,0 +1,22 @@
+#nullable enable
+System.Threading.Tasks.Flow.IDefaultTaskFlowFactory
+System.Threading.Tasks.Flow.IDefaultTaskFlowFactory.Create(System.Threading.Tasks.Flow.TaskFlowOptions! options) -> System.Threading.Tasks.Flow.ITaskFlow!
+System.Threading.Tasks.Flow.INamedConfigureTaskFlowChain
+System.Threading.Tasks.Flow.INamedConfigureTaskFlowChain.ConfigureChain(System.Threading.Tasks.Flow.ITaskScheduler! taskScheduler) -> System.Threading.Tasks.Flow.ITaskScheduler!
+System.Threading.Tasks.Flow.INamedConfigureTaskFlowChain.Name.get -> string!
+System.Threading.Tasks.Flow.INamedConfigureTaskFlowOptions
+System.Threading.Tasks.Flow.INamedConfigureTaskFlowOptions.Configure() -> System.Threading.Tasks.Flow.TaskFlowOptions!
+System.Threading.Tasks.Flow.INamedConfigureTaskFlowOptions.Name.get -> string!
+System.Threading.Tasks.Flow.INamedTaskFlowFactory
+System.Threading.Tasks.Flow.INamedTaskFlowFactory.Create(System.Threading.Tasks.Flow.TaskFlowOptions! options) -> System.Threading.Tasks.Flow.ITaskFlow!
+System.Threading.Tasks.Flow.INamedTaskFlowFactory.Name.get -> string!
+System.Threading.Tasks.Flow.ITaskFlowFactory
+System.Threading.Tasks.Flow.ITaskFlowFactory.CreateTaskFlow(string? name = null) -> System.Threading.Tasks.Flow.ITaskFlow!
+System.Threading.Tasks.Flow.ServiceCollectionExtensions
+System.Threading.Tasks.Flow.TaskFlowFactory
+System.Threading.Tasks.Flow.TaskFlowFactory.CreateTaskFlow(string? name = null) -> System.Threading.Tasks.Flow.ITaskFlow!
+System.Threading.Tasks.Flow.TaskFlowFactory.TaskFlowFactory(System.Collections.Generic.IEnumerable! namedTaskFlowFactories, System.Collections.Generic.IEnumerable! namedConfigureTaskFlowChains, System.Collections.Generic.IEnumerable! namedConfigureTaskFlowOptions, System.Threading.Tasks.Flow.IDefaultTaskFlowFactory! defaultTaskFlowFactory) -> void
+static System.Threading.Tasks.Flow.ServiceCollectionExtensions.AddTaskFlow(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
+static System.Threading.Tasks.Flow.ServiceCollectionExtensions.AddTaskFlow(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string! name, System.Threading.Tasks.Flow.TaskFlowOptions! options) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
+static System.Threading.Tasks.Flow.ServiceCollectionExtensions.AddTaskFlow(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string? name, System.Func? baseTaskFlowFactory = null, System.Func? configureOptions = null, System.Func? configureSchedulerChain = null) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
+static System.Threading.Tasks.Flow.ServiceCollectionExtensions.AddTaskFlow(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Threading.Tasks.Flow.TaskFlowOptions! options) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
diff --git a/TaskFlow.Extensions.Microsoft.DependencyInjection/PublicAPI.Unshipped.txt b/TaskFlow.Extensions.Microsoft.DependencyInjection/PublicAPI.Unshipped.txt
new file mode 100644
index 0000000..7dc5c58
--- /dev/null
+++ b/TaskFlow.Extensions.Microsoft.DependencyInjection/PublicAPI.Unshipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/TaskFlow.Extensions.Microsoft.Logging/PublicAPI.Shipped.txt b/TaskFlow.Extensions.Microsoft.Logging/PublicAPI.Shipped.txt
new file mode 100644
index 0000000..7dc5c58
--- /dev/null
+++ b/TaskFlow.Extensions.Microsoft.Logging/PublicAPI.Shipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/TaskFlow.Extensions.Microsoft.Logging/PublicAPI.Unshipped.txt b/TaskFlow.Extensions.Microsoft.Logging/PublicAPI.Unshipped.txt
new file mode 100644
index 0000000..b4aeba0
--- /dev/null
+++ b/TaskFlow.Extensions.Microsoft.Logging/PublicAPI.Unshipped.txt
@@ -0,0 +1,17 @@
+#nullable enable
+System.Threading.Tasks.Flow.LoggingTaskSchedulerExtensions
+System.Threading.Tasks.Flow.TaskFlowLoggingOptions
+System.Threading.Tasks.Flow.TaskFlowLoggingOptions.CancellationRequestedLogLevel.get -> Microsoft.Extensions.Logging.LogLevel
+System.Threading.Tasks.Flow.TaskFlowLoggingOptions.CancellationRequestedLogLevel.set -> void
+System.Threading.Tasks.Flow.TaskFlowLoggingOptions.EnqueuedLogLevel.get -> Microsoft.Extensions.Logging.LogLevel
+System.Threading.Tasks.Flow.TaskFlowLoggingOptions.EnqueuedLogLevel.set -> void
+System.Threading.Tasks.Flow.TaskFlowLoggingOptions.FailedLogLevel.get -> Microsoft.Extensions.Logging.LogLevel
+System.Threading.Tasks.Flow.TaskFlowLoggingOptions.FailedLogLevel.set -> void
+System.Threading.Tasks.Flow.TaskFlowLoggingOptions.FinishedLogLevel.get -> Microsoft.Extensions.Logging.LogLevel
+System.Threading.Tasks.Flow.TaskFlowLoggingOptions.FinishedLogLevel.set -> void
+System.Threading.Tasks.Flow.TaskFlowLoggingOptions.StartedLogLevel.get -> Microsoft.Extensions.Logging.LogLevel
+System.Threading.Tasks.Flow.TaskFlowLoggingOptions.StartedLogLevel.set -> void
+System.Threading.Tasks.Flow.TaskFlowLoggingOptions.SucceededLogLevel.get -> Microsoft.Extensions.Logging.LogLevel
+System.Threading.Tasks.Flow.TaskFlowLoggingOptions.SucceededLogLevel.set -> void
+System.Threading.Tasks.Flow.TaskFlowLoggingOptions.TaskFlowLoggingOptions() -> void
+static System.Threading.Tasks.Flow.LoggingTaskSchedulerExtensions.WithLogging(this System.Threading.Tasks.Flow.ITaskScheduler! taskScheduler, Microsoft.Extensions.Logging.ILogger! logger, System.Action? configure = null) -> System.Threading.Tasks.Flow.ITaskScheduler!
diff --git a/TaskFlow.Extensions.Time/PublicAPI.Shipped.txt b/TaskFlow.Extensions.Time/PublicAPI.Shipped.txt
new file mode 100644
index 0000000..ec6ef1d
--- /dev/null
+++ b/TaskFlow.Extensions.Time/PublicAPI.Shipped.txt
@@ -0,0 +1,3 @@
+#nullable enable
+System.Threading.Tasks.Flow.ThrottlingTaskSchedulerExtensions
+static System.Threading.Tasks.Flow.ThrottlingTaskSchedulerExtensions.WithDebounce(this System.Threading.Tasks.Flow.ITaskScheduler! taskScheduler, System.TimeSpan interval, System.TimeProvider? timeProvider = null) -> System.Threading.Tasks.Flow.ITaskScheduler!
diff --git a/TaskFlow.Extensions.Time/PublicAPI.Unshipped.txt b/TaskFlow.Extensions.Time/PublicAPI.Unshipped.txt
new file mode 100644
index 0000000..d8b459c
--- /dev/null
+++ b/TaskFlow.Extensions.Time/PublicAPI.Unshipped.txt
@@ -0,0 +1,3 @@
+#nullable enable
+*REMOVED*static System.Threading.Tasks.Flow.ThrottlingTaskSchedulerExtensions.WithDebounce(this System.Threading.Tasks.Flow.ITaskScheduler! taskScheduler, System.TimeSpan interval, System.TimeProvider? timeProvider = null) -> System.Threading.Tasks.Flow.ITaskScheduler!
+static System.Threading.Tasks.Flow.ThrottlingTaskSchedulerExtensions.WithThrottle(this System.Threading.Tasks.Flow.ITaskScheduler! taskScheduler, System.TimeSpan interval, System.TimeProvider? timeProvider = null) -> System.Threading.Tasks.Flow.ITaskScheduler!
diff --git a/TaskFlow.Extensions.Time/TaskFlow.Extensions.Time.csproj b/TaskFlow.Extensions.Time/TaskFlow.Extensions.Time.csproj
index d14de4d..6bc120a 100644
--- a/TaskFlow.Extensions.Time/TaskFlow.Extensions.Time.csproj
+++ b/TaskFlow.Extensions.Time/TaskFlow.Extensions.Time.csproj
@@ -1,6 +1,7 @@
netstandard2.0
+ $(DefineConstants);TASKFLOW_EXTENSIONS_TIMESystem.Threading.Tasks.FlowTaskFlow.Extensions.TimeLeading-edge admission throttling for TaskFlow on netstandard2.0 with TimeProvider-based deterministic timing.
diff --git a/TaskFlow/DedicatedThreadTaskFlow.cs b/TaskFlow/DedicatedThreadTaskFlow.cs
index 31b5758..53ad4b0 100644
--- a/TaskFlow/DedicatedThreadTaskFlow.cs
+++ b/TaskFlow/DedicatedThreadTaskFlow.cs
@@ -45,9 +45,8 @@ public sealed class DedicatedThreadTaskFlow : ThreadTaskFlow
private readonly Thread _thread;
///
- /// Initializes a new instance of the class with default options and an optional name.
+ /// Initializes a new instance of the class with default options and the default thread name.
///
- /// Optional name for the dedicated thread. If null or empty, uses the class name.
///
///
/// This constructor uses the default options from .
@@ -56,17 +55,41 @@ public sealed class DedicatedThreadTaskFlow : ThreadTaskFlow
/// The task flow immediately creates and starts a dedicated background thread for processing tasks.
///
///
- public DedicatedThreadTaskFlow(string? name = default)
+ public DedicatedThreadTaskFlow()
+ : this(TaskFlowOptions.Default, null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with default options and the specified thread name.
+ ///
+ /// The dedicated thread name. If null or empty, the class name is used.
+ ///
+ /// The task flow immediately creates and starts a dedicated background thread using the default options from
+ /// .
+ ///
+ public DedicatedThreadTaskFlow(string? name)
: this(TaskFlowOptions.Default, name)
{
}
///
- /// Initializes a new instance of the class with the specified options and an optional name.
+ /// Initializes a new instance of the class with the specified options and the default thread name.
+ ///
+ /// The options that configure the behavior of this task flow.
+ /// is null.
+ /// The task flow immediately creates and starts a dedicated background thread.
+ public DedicatedThreadTaskFlow(TaskFlowOptions options)
+ : this(options, null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with the specified options and thread name.
///
/// The options that configure the behavior of this task flow.
- /// Optional name for the dedicated thread. If null or empty, uses the class name.
- /// Thrown when is null.
+ /// The dedicated thread name. If null or empty, the class name is used.
+ /// is null.
///
///
/// The task flow immediately creates and starts a dedicated background thread for processing tasks.
@@ -75,7 +98,7 @@ public DedicatedThreadTaskFlow(string? name = default)
/// The thread name is useful for debugging and thread identification in thread dumps or profiling tools.
///
///
- public DedicatedThreadTaskFlow(TaskFlowOptions options, string? name = default)
+ public DedicatedThreadTaskFlow(TaskFlowOptions options, string? name)
: base(options)
{
_thread = new Thread(ThreadStart)
diff --git a/TaskFlow/Extensions/ExceptionTaskSchedulerExtensions.cs b/TaskFlow/Extensions/ExceptionTaskSchedulerExtensions.cs
index 62e8a42..b822916 100644
--- a/TaskFlow/Extensions/ExceptionTaskSchedulerExtensions.cs
+++ b/TaskFlow/Extensions/ExceptionTaskSchedulerExtensions.cs
@@ -109,7 +109,6 @@ public static class ExceptionTaskSchedulerExtensions
/// The type of exception to handle. Must derive from .
/// The task scheduler to wrap with error handling.
/// The action to execute when an exception of type occurs. Receives the scheduler instance, the exception, and the operation name annotation if available.
- /// An optional predicate to filter which exceptions should be handled. If null, all exceptions of the specified type are handled.
/// An that observes matching exceptions according to the specified parameters.
/// Thrown when is null.
///
@@ -117,7 +116,20 @@ public static class ExceptionTaskSchedulerExtensions
/// making it convenient for scenarios where operation names are used for error logging and diagnostics.
/// The error handler receives the scheduler instance, allowing for reactive error handling patterns.
///
- public static ITaskScheduler OnError(this ITaskScheduler taskScheduler, Action errorAction, Func? errorFilter = null)
+ public static ITaskScheduler OnError(this ITaskScheduler taskScheduler, Action errorAction)
+ where TException : Exception
+ {
+ return OnError(taskScheduler, errorAction, null);
+ }
+
+ /// Creates a scheduler that observes exceptions of the specified type when they satisfy a filter.
+ /// The exception type to observe.
+ /// The scheduler to decorate.
+ /// The callback that receives the registration scheduler and matching exception.
+ /// The predicate used to select matching exceptions. A null value observes every exception of the specified type.
+ /// A scheduler that invokes for matching failures and then propagates the current failure.
+ /// or is null.
+ public static ITaskScheduler OnError(this ITaskScheduler taskScheduler, Action errorAction, Func? errorFilter)
where TException : Exception
{
return taskScheduler.UseMiddleware(new AnnotatedExceptionMiddleware(errorFilter ?? DefaultErrorFilter, (scheduler, exception, _) => errorAction(scheduler, exception)));
@@ -129,14 +141,26 @@ public static ITaskScheduler OnError(this ITaskScheduler taskSchedul
/// The type of exception to handle. Must derive from .
/// The task scheduler to wrap with error handling.
/// The action to execute when an exception of type occurs. Receives only the exception instance.
- /// An optional predicate to filter which exceptions should be handled. If null, all exceptions of the specified type are handled.
/// An that observes matching exceptions according to the specified parameters.
/// Thrown when or is null.
///
/// This is the simplest error handling overload, suitable for basic error logging or notification scenarios
/// where scheduler access and annotation context are not needed.
///
- public static ITaskScheduler OnError(this ITaskScheduler taskScheduler, Action errorAction, Func? errorFilter = null)
+ public static ITaskScheduler OnError(this ITaskScheduler taskScheduler, Action errorAction)
+ where TException : Exception
+ {
+ return OnError(taskScheduler, errorAction, null);
+ }
+
+ /// Creates a scheduler that observes exceptions of the specified type when they satisfy a filter.
+ /// The exception type to observe.
+ /// The scheduler to decorate.
+ /// The callback that receives each matching exception.
+ /// The predicate used to select matching exceptions. A null value observes every exception of the specified type.
+ /// A scheduler that invokes for matching failures and then propagates the current failure.
+ /// or is null.
+ public static ITaskScheduler OnError(this ITaskScheduler taskScheduler, Action errorAction, Func? errorFilter)
where TException : Exception
{
Argument.NotNull(errorAction);
@@ -182,14 +206,26 @@ public static ITaskScheduler OnError(this ITaskScheduler taskScheduler, ActionThe type of exception to handle. Must derive from .
/// The task scheduler to wrap with error handling.
/// The action to execute when an exception occurs. Receives the scheduler, exception, and operation name annotation.
- /// An optional predicate to filter which exceptions should be handled. If null, all exceptions of the specified type are handled.
/// An that observes matching exceptions with operation name context.
/// Thrown when is null.
///
/// This overload explicitly provides access, making it ideal for
/// scenarios where operation names are consistently used and needed for error context.
///
- public static ITaskScheduler OnError(this ITaskScheduler taskScheduler, Action errorAction, Func? errorFilter = null)
+ public static ITaskScheduler OnError(this ITaskScheduler taskScheduler, Action errorAction)
+ where TException : Exception
+ {
+ return OnError(taskScheduler, errorAction, null);
+ }
+
+ /// Creates a scheduler that observes filtered exceptions with operation-name metadata.
+ /// The exception type to observe.
+ /// The scheduler to decorate.
+ /// The callback receiving the registration scheduler, matching exception, and captured operation-name annotation.
+ /// The predicate used to select matching exceptions. A null value observes every exception of the specified type.
+ /// A scheduler that invokes for matching failures and then propagates the current failure.
+ /// or is null.
+ public static ITaskScheduler OnError(this ITaskScheduler taskScheduler, Action errorAction, Func? errorFilter)
where TException : Exception
{
return taskScheduler.UseMiddleware(new AnnotatedExceptionMiddleware(errorFilter ?? DefaultErrorFilter, errorAction));
@@ -202,7 +238,6 @@ public static ITaskScheduler OnError(this ITaskScheduler taskSchedul
/// The type of operation annotation to provide to the error handler. Must implement .
/// The task scheduler to wrap with error handling.
/// The action to execute when an exception occurs. Receives the scheduler, exception, and custom annotation.
- /// An optional predicate to filter which exceptions should be handled. If null, all exceptions of the specified type are handled.
/// An that observes matching exceptions with custom annotation context.
/// Thrown when is null.
///
@@ -210,7 +245,22 @@ public static ITaskScheduler OnError(this ITaskScheduler taskSchedul
/// that implements . This enables rich contextual error handling
/// with application-specific metadata.
///
- public static ITaskScheduler OnError(this ITaskScheduler taskScheduler, Action errorAction, Func? errorFilter = null)
+ public static ITaskScheduler OnError(this ITaskScheduler taskScheduler, Action errorAction)
+ where TException : Exception
+ where TAnnotation : IOperationAnnotation
+ {
+ return OnError(taskScheduler, errorAction, null);
+ }
+
+ /// Creates a scheduler that observes filtered exceptions with custom annotation metadata.
+ /// The exception type to observe.
+ /// The captured annotation type supplied to the callback.
+ /// The scheduler to decorate.
+ /// The callback receiving the registration scheduler, matching exception, and captured annotation.
+ /// The predicate used to select matching exceptions. A null value observes every exception of the specified type.
+ /// A scheduler that invokes for matching failures and then propagates the current failure.
+ /// or is null.
+ public static ITaskScheduler OnError(this ITaskScheduler taskScheduler, Action errorAction, Func? errorFilter)
where TException : Exception
where TAnnotation : IOperationAnnotation
{
diff --git a/TaskFlow/Extensions/ThrottlingTaskSchedulerExtensions.cs b/TaskFlow/Extensions/ThrottlingTaskSchedulerExtensions.cs
index 2493b65..4767c26 100644
--- a/TaskFlow/Extensions/ThrottlingTaskSchedulerExtensions.cs
+++ b/TaskFlow/Extensions/ThrottlingTaskSchedulerExtensions.cs
@@ -1,5 +1,6 @@
namespace System.Threading.Tasks.Flow
{
+ using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks.Flow.Annotations;
///
@@ -58,6 +59,9 @@ namespace System.Threading.Tasks.Flow
/// await testScheduler.Enqueue(() => SomeOperation()); // Should succeed
///
///
+#if !TASKFLOW_EXTENSIONS_TIME
+ [SuppressMessage("ApiDesign", "RS0016:Add public types and members to the declared API", Justification = "This API is conditionally unavailable on netstandard2.0 and predates the shared API baseline.")]
+#endif
public static class ThrottlingTaskSchedulerExtensions
{
///
diff --git a/TaskFlow/PublicAPI.Shipped.txt b/TaskFlow/PublicAPI.Shipped.txt
new file mode 100644
index 0000000..745dbd6
--- /dev/null
+++ b/TaskFlow/PublicAPI.Shipped.txt
@@ -0,0 +1,144 @@
+#nullable enable
+abstract System.Threading.Tasks.Flow.TaskFlowBase.Enqueue(System.Func