44

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?

user2341923
  • 4,537
  • 6
  • 30
  • 44
  • 6
    Note that this only says that *getting the enumerable in the first place* is async - it doesn't make it any kind of async enumerator. What is it that you actually want to do? Because I suspect this won't achieve it. – Marc Gravell Apr 25 '14 at 13:54
  • 3
    check http://asyncenum.codeplex.com/ – daryal Apr 25 '14 at 13:56
  • I want to run this function to work with a stream on a worker thread and consume awaited result on the UI thread. – user2341923 Apr 25 '14 at 14:03
  • If you're looking to use `yield` because your platform doesn't have `async/await`, check [Stephen Toubs's `Iterate`](http://blogs.msdn.com/b/pfxteam/archive/2010/11/21/10094564.aspx?Redirected=true). – noseratio Apr 25 '14 at 23:47
  • This seems like a good fit for [TPL DataFlow](https://msdn.microsoft.com/en-us/library/hh228603%28v=vs.110%29.aspx). – spender Sep 07 '15 at 11:51

3 Answers3

32

A more "batteries-included" implementation of this kind of thing, including language support, is now available as of C# 8.0.

Now, when using at least C# 8.0 (or higher) with .NET Standard 2.1 (or higher) and/or .NET Core 3.0 (or higher), the code from the original question may be written as follows:

private async IAsyncEnumerable<char> TestAsync(string testString)
{
    foreach (char c in testString.ToCharArray())
    {
        // do other work, which may include "await"
        yield return c;
    }
}
Joe Amenta
  • 4,662
  • 2
  • 29
  • 38
27

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 MIT) (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).

blenderfreaky
  • 738
  • 7
  • 26
Joe Amenta
  • 4,662
  • 2
  • 29
  • 38
  • 8
    Now you can use [IAsyncEnumerable](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.iasyncenumerable-1?view=netcore-3.0) for this. It is shipped with C#8 .Net Core 3.0 or via Nuget. – joe Jul 11 '19 at 06:23
  • @joe that's actually another answer I put on the same question :-) – Joe Amenta Jul 21 '19 at 18:53
  • Could you please make `Update: IAsyncEnumerable` more prominent? (Maybe even move it to the top of the answer). – tymtam Oct 29 '19 at 00:31
  • @tymtam hm, someone edited that onto this answer, even though `IAsyncEnumerable` is actually a completely different thing that I'd already listed as a separate answer (https://stackoverflow.com/a/46198386/1083771)... I'm going to ask on the meta when I get some time what the best thing to do is... – Joe Amenta Oct 29 '19 at 20:03
  • Apparently I can actually reject an edit, so I've just done that. I've also edited my other answer which said the same thing, to include the details that have been finalized since I originally write the answer – Joe Amenta Dec 11 '19 at 20:08
15

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());
John Gietzen
  • 48,783
  • 32
  • 145
  • 190