Skip to content

Delay with delayDurationSelector duplicates items when the delay is zero #1625

@davidnemeti

Description

@davidnemeti

The problem

The Observable.Delay overload which has a delayDurationSelector parameter seems to be buggy, because it duplicates the items when the delay is zero (or small enough?). Obviously, the expected behavior is not to duplicate the items.

I am experiencing this wrong behavior both with System.Reactive 3.0.0 and 5.0.0.

Wrong behavior

The wrong behavior occurs in the following examples:

Observable.Range(0, 5)
    .Delay(num => Observable.Return(Unit.Default))
    .Subscribe(Console.WriteLine);
    // prints out duplicated items: 0 0 1 1 2 2 3 3 4 4

Observable.Range(0, 5)
    .Delay(num => Observable.FromAsync(async () => {}))
    .Subscribe(Console.WriteLine);
    // prints out duplicated items: 0 0 1 1 2 2 3 3 4 4

Observable.Range(0, 5)
    .Delay(num => Observable.FromAsync(() => Task.CompletedTask))
    .Subscribe(Console.WriteLine);
    // prints out duplicated items: 0 0 1 1 2 2 3 3 4 4

Observable.Range(0, 5)
    .Delay(num => Observable.FromAsync(() => Task.Delay(TimeSpan.Zero)))
    .Subscribe(Console.WriteLine);
    // prints out duplicated items: 0 0 1 1 2 2 3 3 4 4

Observable.Range(0, 5)
    .Delay(num => Observable.FromAsync(async () => Thread.Sleep(TimeSpan.FromMilliseconds(100))))
    .Subscribe(Console.WriteLine);
    // prints out duplicated items: 0 0 1 1 2 2 3 3 4 4

So, the problem is not just the Observable.FromAsync invocation, because a simple Observable.Return can also reproduce the problem.

Also, by looking at the Observable.FromAsync + Thread.Sleep combo, it seems that the problem occurs if there is no real asynchronicity, i.e. when the delayDurationSelector observable fires immediately.

Good behavior

However, the following examples behave as expected, i.e. there is no duplication:

Observable.Range(0, 5)
    .Delay(num => Observable.Empty(Unit.Default))
    .Subscribe(Console.WriteLine);
    // prints out items as expected: 0 1 2 3 4

Observable.Range(0, 5)
    .Delay(num => Observable.Defer(() => Observable.Start(() => { })))
    .Subscribe(Console.WriteLine);
    // prints out items as expected: 0 1 2 3 4

Observable.Range(0, 5)
    .Delay(num => Observable.Defer(() => Observable.Start(() => Thread.Sleep(TimeSpan.Zero))))
    .Subscribe(Console.WriteLine);
    // prints out items as expected: 0 1 2 3 4

Observable.Range(0, 5)
    .Delay(num => Observable.FromAsync(() => Task.Delay(TimeSpan.FromMilliseconds(1))))
    .Subscribe(Console.WriteLine);
    // prints out items as expected (in an arbitrary order): 0 1 2 3 4

So, it seems that the synchronous case is okay, and the Observable.Empty as well. If the delay is large enough, then the problem does not occur either, not even in the asynchronous case.

Good behavior for the simpler overload

Note that the simpler Observable.Delay overload with the dueTime parameter does not have this problem:

Observable.Range(0, 5)
    .Delay(TimeSpan.Zero)
    .Subscribe(Console.WriteLine);
    // prints out items as expected: 0 1 2 3 4

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions