问题
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