常规迭代器块(即“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)
}
}
但同样,编译器返回错误:“无法从资源加载消息字符串”。
这是我项目中真正的编程代码
当我有一个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;
}
但似乎我必须这样做。
感谢您的帮助。
答案 0 :(得分:66)
您所描述的内容可以使用Task.WhenAll
方法完成。注意代码如何变成简单的单行代码。会发生什么事情是每个网址开始下载,然后使用WhenAll
将这些操作合并为一个可以等待的Task
。
Task<IEnumerable<string>> DownLoadAllUrls(string[] urls)
{
return Task.WhenAll(from url in urls select DownloadHtmlAsync(url));
}
答案 1 :(得分:65)
tl; dr 使用yield实现的迭代器是一个阻塞构造,所以现在await和yield是不兼容的。
长因为迭代IEnumerable
是阻塞操作,调用标记为async
的方法仍将以阻塞方式执行,因为它必须等待该操作完成。
async Task<IEnumerable<Foo>> Method(String [] Strs)
{
foreach(var str in strs)
{
yield return await DoSomethingAsync( str)
}
}
等待Method
混合了意义。你想等到Task
有一个IEnumerable
,然后阻止迭代吗?或者您是否正在尝试等待IEnumerable
的每个值?
我假设第二个是期望的行为,在这种情况下,现有的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();
}
答案 2 :(得分:19)
根据 C#8.0 (link#1和link#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的回答意味着,调用代码将异步获取并行获得的结果的集合。上面的代码意味着调用代码将以异步方式从流中一对一地获取结果。
答案 3 :(得分:16)
我知道答案太迟了,但这是另一个可以用这个库实现的简单解决方案:
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);
});
}
答案 4 :(得分:4)
答案 5 :(得分:4)
此功能将从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;
}
}
答案 6 :(得分:1)
首先,请记住Async的内容尚未完成。在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();
答案 7 :(得分:1)
遗憾的是,收益率不适用于等待。但这就是Rx的用途。查看https://msdn.microsoft.com/library/hh242985
答案 8 :(得分:-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;
}
}
}