web-dev-qa-db-fra.com

Tâche d'itérateur asynchrone <IEnumerable <T>>

J'essaie d'implémenter une fonction asynchrone qui retourne un itérateur. L'idée est la suivante:

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

Cependant, un message d'erreur indique que la fonction ne peut pas être un bloc d'itérateur car Task<IEnumerable<char>> n'est pas un type d'interface d'itérateur. Y a-t-il une solution?

28
user2341923

Il semble que ce que vous cherchez vraiment soit quelque chose comme IObservable<T> , qui est un peu comme un asynchrone basé sur Push IEnumerable<T>. Voir Reactive Extensions, a.k.a. Rx (code sous licence Apache-2.0) (sans affiliation) pour une multitude de méthodes qui fonctionnent avec IObservable<T> pour le faire fonctionner comme LINQ-to-Objects et plus.

Le problème avec IEnumerable<T> est qu'il n'y a rien qui rend vraiment l'énumération elle-même asynchrone. Si vous ne voulez pas ajouter de dépendance à Rx (ce qui fait vraiment IObservable<T> shine), cette alternative pourrait vous convenir:

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;
    }
}

bien que j'aimerais souligner que sans savoir ce qui est réellement fait de manière asynchrone, il peut y avoir une bien meilleure façon d'atteindre vos objectifs. Aucun du code que vous avez publié ne fera rien de manière asynchrone, et je ne sais pas vraiment si quelque chose dans // do other work est asynchrone (dans ce cas, ce n'est pas une solution à votre problème sous-jacent bien que cela rendra votre code compilable).

Mise à jour: IAsyncEnumerable

Après C # 8 et . Net Standard 2.1. IAsyncEnumerable<T> peut être utilisé pour cela lorsque la lecture à partir de la source doit être asynchrone (par exemple, la lecture à partir d'un flux cloud).

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;
    }
}
22
Joe Amenta

Pour développer les réponses précédentes, vous pouvez utiliser les extensions réactives 'Observable.Create<TResult> famille de méthodes pour faire exactement ce que vous voulez.

Voici un exemple:

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

Voici comment vous pouvez l'utiliser dans LINQPad, par exemple:

// 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());
14
John Gietzen

ne implémentation plus "batteries incluses" de ce genre de chose, y compris le support de la langue, est actuellement (au moment de la rédaction) considéré pour inclusion dans C # 8.0.

Si je comprends bien, cela peut changer à tout moment.

9
Joe Amenta

Actuellement, il n'est pas possible de créer une méthode qui soit à la fois un itérateur et asynchrone. Cela sera possible avec la fonction Async Streams de C # 8.0.

Ce qui est possible actuellement, c'est d'avoir un itérateur de tâches. Quelque chose comme ça:

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

Et consommez-le comme ceci:

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

Cette approche n'est viable que pour les cas les plus simples. Par exemple, si vous devez vérifier une condition asynchrone avant de renvoyer une valeur, vous n'avez pas de chance. Vous devez essentiellement construire une machine d'état à la main. Ou utilisez une bibliothèque comme AsyncEnumerable , qui nécessite quelques modifications de la structure de l'itérateur et de l'énumérateur.

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);
});
0
Theodor Zoulias