是否有可能"等待收益率返回DoSomethingAsync()"

jia*_*hen 98 c# async-await c#-5.0 iasyncenumerable

常规迭代器块(即"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;
}
Run Code Online (Sandbox Code Playgroud)

但是,我收到编译器错误,引用"无法从资源加载消息字符串".

这是另一种尝试:

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

但同样,编译器返回错误:"无法从资源加载消息字符串".


这是我项目中真正的编程代码

当我有一个List Task时,这非常有用,该任务可以从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;
}
Run Code Online (Sandbox Code Playgroud)

但似乎我必须这样做.

谢谢你的帮助.

Arn*_*sen 68

tl; dr使用yield实现的迭代器是一个阻塞构造,所以现在await和yield是不兼容的.

Long因为迭代a IEnumerable是一个阻塞操作,调用标记为的方法async仍然会以阻塞方式执行它,因为它必须等待该操作完成.

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

等待Method混合意义.你想等到Task有一个IEnumerable然后阻止迭代吗?或者你试图等待每个值IEnumerable

我假设第二个是期望的行为,在这种情况下,现有的迭代器语义将不起作用.该IEnumerator<T>接口基本上是

public interface IEnumerator<T>
  T Current;
  bool MoveNext();
}
Run Code Online (Sandbox Code Playgroud)

我忽略了,Reset()因为它对一系列异步结果毫无意义.但你需要的是这样的:

public interface IAsyncEnumerator<T>
  T Current;
  Task<bool> MoveNext();
}
Run Code Online (Sandbox Code Playgroud)

当然,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();
}
Run Code Online (Sandbox Code Playgroud)


Bri*_*eon 67

您所描述的内容可以通过该Task.WhenAll方法完成.注意代码如何变成简单的单行代码.发生的是每个网址开始下载,然后WhenAll用于将这些操作合并为一个Task可以等待的单个操作.

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

  • 最后一行可以更简洁地写成`urls.Select(DownloadHtmlAsync)` (21认同)
  • 这实际上并没有回答"是否有可能等待收益?"的问题.你刚刚找到了这个用例的解决方法.没回答一般问题. (12认同)
  • 在这种情况下不需要async/await.只需从方法中删除`async`并直接返回Task.WhenAll`. (10认同)

Alb*_*rtK 27

根据C#8.0(链接#1链接#2)的新功能,我们将提供IAsyncEnumerable<T>接口支持,以便实现第二次尝试.它看起来像这样:

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);
}
Run Code Online (Sandbox Code Playgroud)

我们可以在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);
}
Run Code Online (Sandbox Code Playgroud)

Brian Gideon的回答暗示调用代码将异步获得并行获得的结果集合.上面的代码暗示调用代码将以异步方式逐个地从流中获得结果.

  • 这应该是新接受的答案,IAsyncEnumerable 对我有用 (3认同)

Ser*_*nov 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);
  });
}
Run Code Online (Sandbox Code Playgroud)


Tha*_* Yu 5

有一个计划要做

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

但目前还不可能

更新:C# 8.0 中添加


Pet*_*son 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;
}
Run Code Online (Sandbox Code Playgroud)

如果您想消耗(或产生)连续的结果流(例如您可能从IoT设备或云服务获得的结果),则不是很有用。那里有异步流。

我们引入了IAsyncEnumerable,这正是您所期望的;IEnumerable的异步版本。该语言使您可以等待这些元素消耗它们的元素,并让它们返回以生成元素。

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