CXuesong / WikiClientLibrary

/*🌻*/ Wiki Client Library is an asynchronous MediaWiki API client library targeting modern .NET platforms
https://github.com/CXuesong/WikiClientLibrary/wiki
Apache License 2.0
82 stars 16 forks source link

A strange behavior when Enumerable.Select is chained with AsyncEnumerable.Take #27

Open CXuesong opened 7 years ago

CXuesong commented 7 years ago

Test case

public class Playground : UnitTestsBase
{

    /// <inheritdoc />
    public Playground(ITestOutputHelper output) : base(output)
    {
    }

    [Fact]
    public async Task Test1()
    {
        var invoked = false;
        var ienu = new DelegateAsyncEnumerable<IEnumerable<string>>(async ct =>
        {
            if (invoked) return null;
            await Task.Yield();
            IEnumerable<string> x = new[] {"abc", "def", "ghi", "jkl"};
            invoked = true;
            return Tuple.Create(x.Select(a => a + "a"), true);
        });
        var y = ienu.SelectMany(t => t.ToAsyncEnumerable());
        var z = await y.Take(4).ToArray();
        ShallowTrace(z);
        Assert.DoesNotContain(null, z);
    }

}

The assertion fails. If I either change Take(4) to Take(3), change Select invocation to x.Select((a, i) => a + "a"), or remove await Task.Yield(); line, the test passes.

It seems that there is a strange interaction between the underlying SelectArrayIterator<TSource, TResult> and thread-context switches. I got a rough idea when looking at Iterator<TSource>.GetEnumerator, but would take another time to dig it deeper.

Related code in library: https://github.com/CXuesong/WikiClientLibrary/blob/435681dad887b160a475ca2c40b6e25d67990d06/WikiClientLibrary/Flow/Board.cs#L108-L111