「yield return DoSomethingAsync()」を待つことは可能ですか?


108

通常のイテレーターブロック(つまり、 "yield return")は、 "async"および "await"と互換性がありませんか?

これは私が何をしようとしているのかについての良い考えを与えます:

async Task<IEnumerable<Foo>> Method(String [] Strs)
{
    // I want to compose the single result to the final result, so I use the SelectMany
    var finalResult = UrlStrings.SelectMany(link =>   //i have an Urlstring Collection 
                   await UrlString.DownLoadHtmlAsync()  //download single result; DownLoadHtmlAsync method will Download the url's html code 
              );
     return finalResult;
}

しかし、「リソースからメッセージ文字列を読み込めません」というコンパイラエラーが表示されます。

ここに別の試みがあります:

async Task<IEnumerable<Foo>> Method(String [] Strs)
{
    foreach(var str in strs)
    {
        yield return await DoSomethingAsync( str)
    }
}

ただし、コンパイラはエラーを返します。「リソースからメッセージ文字列をロードできません」。


これが私のプロジェクトの実際のプログラミングコードです

これは、リストタスクがある場合に非常に便利です。そのタスクは、URLからHTMLをダウンロードでき、構文 "yield return await task"を使用しますIEnumerable<Foo>。結果はです。私はこのコードを書きたくありません:

async Task<IEnumerable<String>> DownLoadAllURL(String [] Strs)
{
    List<Foo> htmls= new ...
    foreach(var str in strs)
    {
        var html= await DownLoadHtmlAsync( str)
        htmls.Add(item)
    }
    return htmls;
}

しかし、私はしなければならないようです。

助けてくれてありがとう。


4
この質問は特に明確ではありません。やりたいことを正確に説明する必要があります。コードサンプルだけから何をしたいのかを判断するのは困難です。
ジャスティン

3
Ix_Experimental-非同期NuGetパッケージは、 LINQをサポートする完全な「非同期列挙子」が含まれます。IAsyncEnumerator<T>Arneで定義されているタイプを使用します。
スティーブンクリアリー2011

@StephenClearyそのパッケージは除外されました。これは何年も前であり、MSのrxteamによって作成されたものなので、それがRXに組み込まれたかどうかを知っていますか?
JoeBrockhaus、2015

@JoeBrockhaus:Ix-Asyncとして存続しているように見えます。
Stephen Cleary 2015

回答:


72

あなたが説明していることは、Task.WhenAllメソッドで達成できます。コードが単純なワンライナーにどのように変わるかに注目してください。発生するのは、個々のURLのそれぞれがダウンロードをWhenAll開始し、それらの操作を組み合わせて、Task待機可能な単一の操作にすることです。

Task<IEnumerable<string>> DownLoadAllUrls(string[] urls)
{
    return Task.WhenAll(from url in urls select DownloadHtmlAsync(url));
}

10
この場合、非同期/待機は必要ありません。asyncメソッドから削除してreturn Task.WhenAll直接行うだけです。
luiscubal 2013年

22
最終行は次のように簡潔に書くことができますurls.Select(DownloadHtmlAsync)
BlueRaja-Danny Pflughoeft

1
これが私が直面していた正確な問題であることが判明しました(一連のURIから文字列を遅延ダウンロードする)。ありがとうございました。
James Ko

13
これは実際にIs it possible to await yield? は、この1つのユースケースの回避策を見つけたという質問には答えません。一般的な質問には答えませんでした。
Buh Buh

1
Task<string[]>これは、実行が遅延されたイテレータを返さなくなったことを示しているため、戻る傾向があります。すべてのURLがダウンロードされています。
johnnycardy 2018年

73

tl; dr yieldで実装されたイテレーターはブロッキング構造であるため、現時点では待機していて、yieldには互換性がありません。

Longの反復IEnumerableはブロッキング操作であるため、マークが付けられたメソッドを呼び出してasyncも、その操作が完了するまで待機する必要があるため、ブロッキング方式で実行されます。

async Task<IEnumerable<Foo>> Method(String [] Strs)
{
  foreach(var str in strs)
  {
    yield return await DoSomethingAsync( str)
  }
}  

待っているMethodミックス意味。あなたがされるまで待ちたいかTaskありIEnumerable、その後、それを反復処理をブロック?それとも、それぞれの値を待つつもりIEnumerableですか?

2番目は望ましい動作であり、その場合、既存のイテレーターのセマンティクスは機能しません。IEnumerator<T>インタフェースは基本的にあり

public interface IEnumerator<T>
  T Current;
  bool MoveNext();
}

Reset()非同期の結果のシーケンスには意味がないため、無視しています。しかし、必要なのは次のようなものです。

public interface IAsyncEnumerator<T>
  T Current;
  Task<bool> MoveNext();
}

もちろん、これforeachも機能せず、次のように手動で繰り返す必要があります。

var moveNext = await asyncEnumerator.MoveNext();
while(moveNext) {

  // get the value that was fetche asynchronously
  var v = asyncEnumerator.Current;

  // do something with that value

  // suspend current execution context until next value arrives or we are done
  moveNext = await asyncEnumerator.MoveNext();
}

1
次のC#言語バージョンがforeachあなたの仮説のサポートを追加する可能性があると思いますIAsyncEnumerator<T>か?

1
@Dai非常にパイプラインの中にあると思います。C#8.0の場合/それについて読んでいました。
nawfal

@Dai、docs.microsoft.com /en-us/ dotnet /csharp/ whats -new/
Victor Yarema

40

C#8.0の新機能(link#1およびlink#2)にIAsyncEnumerable<T>よると、2回目の試行を実装できるインターフェースのサポートがあります。次のようになります。

async Task<Foo> DoSomethingAsync(string url)
{
    ...
}       
// producing IAsyncEnumerable<T>
async IAsyncEnumerable<Foo> DownLoadAllURL(string[] strs)
{
    foreach (string url in strs)
    {
        yield return await DoSomethingAsync(url);
    }
}
...
// using
await foreach (Foo foo in DownLoadAllURL(new string[] { "url1", "url2" }))
{
    Use(foo);
}

C#5でも同じ動作を実現できますが、セマンティクスは異なります。

async Task<Foo> DoSomethingAsync(string url)
{
    ...
}
IEnumerable<Task<Foo>> DownLoadAllURL(string[] strs)
{
    foreach (string url in strs)
    {
        yield return DoSomethingAsync(url);
    }
}

// using
foreach (Task<Foo> task in DownLoadAllURL(new string[] { "url1", "url2" }))
{
    Foo foo = await task;
    Use(foo);
}

ブライアンギデオンの答えは、呼び出し元のコードが並行して取得された結果のコレクションを非同期で取得することを意味します。上記のコードは、呼び出しコードがストリームから1つずつ非同期的に結果を取得することを意味します。


17

私は答えを手遅れだということを知っているが、ここではこのライブラリを用いて達成することができる別のシンプルなソリューションです:
GitHubの:https://github.com/tyrotoxin/AsyncEnumerable
NuGet.org:HTTPS://www.nuget .org / packages / AsyncEnumerator /
Rxよりもはるかに単純です。

using System.Collections.Async;

static IAsyncEnumerable<string> ProduceItems(string[] urls)
{
  return new AsyncEnumerable<string>(async yield => {
    foreach (var url in urls) {
      var html = await UrlString.DownLoadHtmlAsync(url);
      await yield.ReturnAsync(html);
    }
  });
}

static async Task ConsumeItemsAsync(string[] urls)
{
  await ProduceItems(urls).ForEachAsync(async html => {
    await Console.Out.WriteLineAsync(html);
  });
}

5

この機能は、C#8.0以降で使用できます。https://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/

MSDNから

非同期ストリーム

C#5.0のasync / await機能を使用すると、コールバックなしで簡単なコードで非同期の結果を消費(および生成)できます。

async Task<int> GetBigResultAsync()
{
    var result = await GetResultAsync();
    if (result > 20) return result; 
    else return -1;
}

IoTデバイスやクラウドサービスから取得するような結果の連続したストリームを消費(または生成)する場合は、それほど役に立ちません。そのために非同期ストリームがあります。

IAsyncEnumerableを紹介します。これはまさにあなたが期待するものです。IEnumerableの非同期バージョン。この言語を使用すると、これらの要素を消費して要素を消費し、要素を生成するためにそれらに戻ることができます。

async IAsyncEnumerable<int> GetBigResultsAsync()
{
    await foreach (var result in GetResultsAsync())
    {
        if (result > 20) yield return result; 
    }
}


1

まず第一に、非同期のものは終了していないことを覚えておいてください。C#チームは、C#5がリリースされるまでにはまだ長い道のりがあります。

そうは言ってもDownloadAllHtml、別の方法で関数内で発生しているタスクを収集したいと思うかもしれません。

たとえば、次のようなものを使用できます。

IEnumerable<Task<string>> DownloadAllUrl(string[] urls)
{
    foreach(var url in urls)
    {
        yield return DownloadHtmlAsync(url);
    }
}

async Task<string> DownloadHtmlAsync(url)
{
    // Do your downloading here...
}

ないというDownloadAllUrl機能がありませ非同期呼び出し。ただし、非同期呼び出しを別の関数(つまりDownloadHtmlAsync)に実装することができます。

タスク並列ライブラリには.ContinueWhenAnyおよび.ContinueWhenAll関数があります。

これは次のように使用できます。

var tasks = DownloadAllUrl(...);
var tasksArray = tasks.ToArray();
var continuation = Task.Factory.ContinueWhenAll(tasksArray, completedTasks =>
{
    completedtask
});
continuation.RunSynchronously();

メソッドをasyncとして定義した場合、(ループの前に)「ヘッダー」を持つこともできますTask<IEnumerable<Task<string>>> DownloadAllUrl。または、「フッター」アクションが必要な場合IEnumerable<Task>。例:gist.github.com/1184435
ジョナサンディキンソン

1
今というasyncものがされて終了し、C#5.0がされてリリースされ、これをすることができます更新すること。
casperOne

私はこれが好きですが、この場合、どのようにforeach(var url in await GetUrls()){yield return GetContent(url)}; 私は2つの方法で分割する方法を見つけました。
ファンパブロガルシアコエロ2017年


-1

このソリューションは期待どおりに機能します。await Task.Run(() => enumerator.MoveNext())パーツに注意してください。

using (var enumerator = myEnumerable.GetEnumerator())
{
    while (true)
    {
        if (enumerator.Current != null)
        {
            //TODO: do something with enumerator.Current
        }

        var enumeratorClone = monitorsEnumerator;
        var hasNext = await Task.Run(() => enumeratorClone.MoveNext());
        if (!hasNext)
        {
            break;
        }
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.