Skip to content

Should TimeProviderTaskExtensions run continuations synchronously? #85326

Description

@egil

From a test ergonomics point of view, having the backward-compatible TimeProvider extensions methods run continuations asynchronously is bad for business.

I have a ManualTimeProvider implementation I am currently using included in https://github.com/egil/TimeScheduler, where I ported the TimeProvider type to .NET 6 as well as the TimeProviderTaskExtensions type. However, in my implementation, I changed the following:

public DelayState() : base(TaskCreationOptions.RunContinuationsAsynchronously) {}

to

public DelayState() : base() { }

This allows me to write the following test:

[Fact]
public void Callbacks_runs_synchronously()
{
    // arrange
    var sut = new ManualTimeProvider();
    var callbackCount = 0;
    _ = Continuation(sut, () => callbackCount++);

    // act
    sut.ForwardTime(TimeSpan.FromSeconds(10));

    // assert
    callbackCount.Should().Be(1);

    static async Task Continuation(TimeProvider timeProvider, Action callback)
    {
        await timeProvider.Delay(TimeSpan.FromSeconds(10));
        callback();
    }
}

With the original version that uses TaskCreationOptions.RunContinuationsAsynchronously, I have to add a Task.Yield() or similar into the test after forwarding time, otherwise the test will fail.

[Fact]
public async void Callbacks_runs_asynchronously()
{
    // arrange
    var sut = new ManualTimeProvider();
    var callbackCount = 0;
    _ = Continuation(sut, () => callbackCount++);

    // act
    sut.ForwardTime(TimeSpan.FromSeconds(10));
+    await Task.Yield();

    // assert
    callbackCount.Should().Be(1);

    static async Task Continuation(TimeProvider timeProvider, Action callback)
    {
        await timeProvider.Delay(TimeSpan.FromSeconds(10));
        callback();
    }
}

It is a small change, but it does make the test less readable, adds noise, and more importantly, makes the test non-deterministic.

cc. @tarekgh.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions