ChatGPT解决这个技术问题 Extra ChatGPT

是否可以“等待收益返回 DoSomethingAsync()”

常规迭代器块(即“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;
}

但似乎我必须这样做。

谢谢你的帮助。

这个问题不是特别清楚,您应该详细说明您想要做什么 - 仅从代码示例很难确定您想要做什么。
Ix_Experimental-Async NuGet package 包括带有 LINQ 支持的“异步枚举器”。他们使用 Arne 定义的 IAsyncEnumerator<T> 类型。
@StephenCleary 该包裹已被除名。由于这是很久以前并且由 MS 的 rxteam 编写的,你知道它是否被纳入 RX 吗?
@JoeBrockhaus:看起来像 lives on as Ix-Async

B
Brian Gideon

您所描述的可以使用 Task.WhenAll 方法完成。注意代码是如何变成一个简单的单行代码的。发生的情况是每个单独的 url 开始下载,然后使用 WhenAll 将这些操作组合成一个可以等待的 Task

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

在这种情况下不需要 async/await。只需从方法中删除 async 并直接执行 return Task.WhenAll
最后一行可以更简洁地写成 urls.Select(DownloadHtmlAsync)
事实证明,这正是我面临的问题(从一系列 URI 中懒惰地下载字符串)。谢谢你。
这实际上并不能回答 Is it possible to await yield? 您刚刚为这个用例找到了解决方法的问题。没有回答一般问题。
我倾向于返回 Task<string[]> 因为这表明您不再返回延迟执行的迭代器,即。正在下载所有 URL。
E
ESG

tl; dr 用 yield 实现的迭代器是一个阻塞结构,所以目前 await 和 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 的每个值?

我假设第二个是期望的行为,在这种情况下,现有的迭代器语义将不起作用。 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();
}

您认为下一个 C# 语言版本可能会为您假设的 IAsyncEnumerator<T> 添加 foreach 支持吗?
@Dai 我认为它正在筹备中。对于 C# 8.0/ 已经阅读过它。
A
AlbertK

根据 C# 8.0link#1link#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);
}

我们可以在 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 的回答暗示调用代码将异步获得一组并行获得的结果。上面的代码暗示调用代码将以异步方式从流中一一获取结果。


S
Serge Semenov

我知道我回答得太晚了,但这里有另一个可以使用此库实现的简单解决方案:
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);
  });
}

P
Petter Pettersson

此功能将从 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;
}

如果您想要消费(或产生)连续的结果流,例如您可能从物联网设备或云服务获得的结果,那么它就没有多大帮助。异步流就是为此而存在的。

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

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

T
Thaina Yu

有一个计划要做

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

但目前不可能


J
John Gietzen

首先,请记住,异步的东西还没有完成。在 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();

如果您将方法定义为异步 Task<IEnumerable<Task<string>>> DownloadAllUrl,您还可以有一个“标题”(在循环之前)。或者,如果您想要“页脚”操作 IEnumerable<Task>。例如gist.github.com/1184435
现在 async 的东西 is 已经完成并且 C# 5.0 发布了,这个 可以 更新了。
我喜欢这个,但是在这种情况下 foreach(var url in await GetUrls()) { yield return GetContent(url) };我只找到了一种分成两种方法的方法。
DownloadAllUrl 方法的有趣之处在于,说它不是异步调用并不完全正确。阻止它立即异步运行的东西实际上是 yield 关键字。只要您遍历返回的 IEnumerable,所有任务都会启动。是否正在等待任务是另一个问题。
J
Johan Franzén

不幸的是,收益不适用于等待。但这就是 Rx 的用途。查看https://msdn.microsoft.com/library/hh242985


F
Francois

此解决方案按预期工作。注意 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;
        }
    }
}