Asynchronous iterator Task<IEnumerable<T>>

瘦欲@ 提交于 2019-11-26 11:16:17

问题


I’m trying to implement an asynchronous function that returns an iterator. The idea is the following:

    private async Task<IEnumerable<char>> TestAsync(string testString)
    {
        foreach (char c in testString.ToCharArray())
        {
            // do other work
            yield return c;
        }
    }

However, there is an error message that the function cannot be an iterator block because Task<IEnumerable<char>> is not an iterator interface type. Is there a solution?


回答1:


It sounds like what you may really be looking for is something like IObservable<T>, which is sort of like a push-based asynchronous IEnumerable<T>. See Reactive Extensions, a.k.a. Rx (code licensed under Apache-2.0) (no affiliation) for a huge host of methods that work with IObservable<T> to make it work like LINQ-to-Objects and more.

The problem with IEnumerable<T> is that there's nothing that really makes the enumeration itself asynchronous. If you don't want to add a dependency on Rx (which is really what makes IObservable<T> shine), this alternative might work for you:

public async Task<IEnumerable<char>> TestAsync(string testString)
{
    return GetChars(testString);
}

private static IEnumerable<char> GetChars(string testString)
{
    foreach (char c in testString.ToCharArray())
    {
        // do other work
        yield return c;
    }
}

though I'd like to point out that without knowing what's actually being done asynchronously, there may be a much better way to accomplish your goals. None of the code you posted will actually do anything asynchronously, and I don't really know if anything in // do other work is asynchronous (in which case, this isn't a solution to your underlying problem though it will make your code compile).

Update: IAsyncEnumerable

After C#8 and .Net Standard 2.1. IAsyncEnumerable<T> can be used for this when reading from the source needs to be async(e.g. Reading from a cloud stream).

public async void Run(string path)
{
    IAsyncEnumerable<string> lines = TestAsync(new StreamReader(path));
    await foreach (var line in lines)
    {
        Console.WriteLine(line);
    }
}

private async IAsyncEnumerable<string> TestAsync(StreamReader sr)
{
    while (true)
    {
        string line = await sr.ReadLineAsync();
        if (line == null)
            break;
        yield return line;
    }
}



回答2:


To elaborate on previous answers, you can use Reactive Extensions' Observable.Create<TResult> family of methods to do exactly what you want.

Here's an example:

var observable = Observable.Create<char>(async (observer, cancel) =>
{
    for (var i = 0; !cancel.IsCancellationRequested && i < 100; i++)
    {
        observer.OnNext(await GetCharAsync());
    }
});

Here's how you can use it in LINQPad, for example:

// Create a disposable that keeps the query running.
// This is necessary, since the observable is 100% async.
var end = Util.KeepRunning();

observable.Subscribe(
    c => Console.WriteLine(c.ToString()),
    () => end.Dispose());



回答3:


A more "batteries-included" implementation of this kind of thing, including language support, is currently (at the time of writing) being considered for inclusion in C# 8.0.

As I understand it, this is subject to change at any time.




回答4:


Currently it is not possible to create a method that is both an iterator and asynchronous. It will be possible with the Async Streams feature of C# 8.0.

What is possible right now is to have an iterator of tasks. Something like this:

private IEnumerable<Task<char>> GetChars(string testString)
{
    foreach (char c in testString.ToCharArray())
    {
        yield return Task.FromResult(c);
    }
}

And consume it like this:

foreach (var asyncChar in GetChars("Armadillo"))
{
    var c = await asyncChar;
}

This approach is viable only for the simplest of cases. For example if you have to check an async condition before returning a value, then you are out of luck. You essentially have to build a state machine by hand. Or use a library like the AsyncEnumerable, which requires some changes to the structure of both the iterator and the enumerator.

private IAsyncEnumerable<char> GetChars(string testString)
{
    return new AsyncEnumerable<char>(async yield =>
    {
        foreach (char c in testString.ToCharArray())
        {
            await yield.ReturnAsync(c);
        }
    });
}

await GetChars("Armadillo").ForEachAsync(async c =>
{
    await Console.Out.WriteLineAsync(c);
});


来源:https://stackoverflow.com/questions/23295119/asynchronous-iterator-taskienumerablet

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!