Skip to content

[API Proposal]: Add async overload of ChangeToken.OnChange #69099

@davidfowl

Description

@davidfowl

Background and motivation

Today it's not possible to use ChangeToken.OnChange to execute asynchronous logic before re-subscribing for new updates. The current API has a Action<T> based callback only (https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.primitives.changetoken.onchange?view=dotnet-plat-ext-6.0). We need an async overload to run async logic so people don't end up using async void or blocking when using this helper.

API Proposal

 namespace Microsoft.Extensions.Primitives;

 public static class ChangeToken
 {
     public static IDisposable OnChange(Func<IChangeToken?> changeTokenProducer, Action changeTokenConsumer);
     public static IDisposable OnChange<TState>(Func<IChangeToken?> changeTokenProducer, Action<TState> changeTokenConsumer, TState state);
+    public static IDisposable OnChange(Func<IChangeToken?> changeTokenProducer, Func<Task> changeTokenConsumer);
+    public static IDisposable OnChange<TState>(Func<IChangeToken?> changeTokenProducer, Func<TState, Task> changeTokenConsumer, TState state);
 }

API Usage

using Microsoft.Extensions.Primitives;

var config = new ConfigurationBuilder().AddJsonFile("config.json", optional: false, reloadOnChange: true)
            .Build();

_ = ChangeToken.OnChange(config.GetReloadToken, async () =>
{
    await Task.Delay(1000);
    Console.WriteLine("Change happened");
});

Alternatives (added by @svick)

  • Add a CancellationToken parameter that's canceled when a following change is detected. This would probably be confusing, since not every caller would want to honor it and can be implemented by users if needed.
  • Change the name to OnAsyncChange (or similar). For those that use async lambda with OnChange today, this would be less breaking, but they wouldn't automatically get the benefit of the new version.

Risk (added by @svick)

This change means that existing code that uses an async lambda when calling OnChange will change meaning: previously, it compiled to an async void method; with this change, it will be an async Task method, where the callback is re-registered only once the returned Task completes. This could break some users, though the new behavior should be more correct.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions