是否可以“等待产量返回 DoSomething Async()”

常规迭代器块(例如“屈服返回”)是否与“异步”和“等待”不兼容?

这就很好地说明了我正在努力做的事情:

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 并且我使用语法“结果返回等待任务”,结果是我想要 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;
}

但似乎我必须这么做。

谢谢你的帮助。

93241 次浏览

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

使用屈服实现的迭代器是一个阻塞构造,因此目前等待和屈服是不兼容的。

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();
}

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

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

我知道我的答案已经太迟了,但这里有另一个简单的解决方案,可以用这个库来实现:
GitHub: < a href = “ https://GitHub.com/tyrotox/AsyncEnumable”rel = “ noReferrer”> https://GitHub.com/tyrotoxin/asyncenumerable
NuGet.org: < a href = “ https://www.NuGet.org/package/AsyncEnumerator/”rel = “ noReferrer”> 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);
});
}

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

不幸的是,收益率与等待无关。但这就是 Rx 的作用。看看 https://msdn.microsoft.com/library/hh242985

有个计划

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

但目前还不可能

更新: 它是在 C # 8.0中添加的

这个特性将在 C # 8.0. https://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/中提供

来自 MSDN

异步流

C # 5.0的异步/等待特性使您能够在简单的代码中使用(并生成)异步结果,而不需要回调:

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

如果你想消费(或产生)连续的结果流,比如你可能从物联网设备或云服务获得的结果,那么这种方法就没有多大帮助。异步数据流就是为此而存在的。

我们引入了 IAsyncEnumable,这正是您所期望的; 一个 IEnumable 的异步版本。该语言允许您等待 foreach 使用它们的元素,然后返回给它们以生成元素。

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

根据 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);
}

我们可以在 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 的回答意味着调用代码将异步获得并行获得的结果集合。上面的代码意味着调用代码将以异步方式从流中一个接一个地获得结果。