130

通常のイテレータブロック(つまり「yieldreturn」)は「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

9 に答える 9

80

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

Task<IEnumerable<string>> DownLoadAllUrls(string[] urls)
{
    return Task.WhenAll(from url in urls select DownloadHtmlAsync(url));
}
于 2011-07-08T18:06:12.640 に答える
77

tl; dr yieldで実装されたイテレータはブロッキング構造であるため、現時点ではwaitと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番目は望ましい動作であり、その場合、既存のIteratorセマンティクスは機能しないと思います。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();
}
于 2011-05-30T05:42:07.927 に答える
58

C#8.0の新機能(リンク#1リンク#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);
}

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

于 2018-11-13T23:40:43.787 に答える
19

答えが遅すぎることはわかっていますが、このライブラリで実現できる別の簡単な解決策は次の とおりです。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);
  });
}
于 2016-02-25T20:01:03.050 に答える
6

この機能は、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; 
    }
}
于 2018-11-13T15:09:41.677 に答える
4

する計画がありました

https://github.com/dotnet/csharplang/issues/43

しかし、現在は不可能です

于 2017-10-05T04:07:38.317 に答える
1

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

そうは言っても、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();
于 2011-02-22T16:17:12.657 に答える
1

残念ながら、yieldはawaitでは機能しません。しかし、これがRxの目的です。https://msdn.microsoft.com/library/hh242985をチェックしてください

于 2017-10-03T12:39:31.837 に答える
-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;
        }
    }
}
于 2017-05-04T08:05:35.383 に答える