Можно ли "дождаться возврата доходности DoSomethingAsync ()" - PullRequest
92 голосов
/ 21 февраля 2011

Являются ли обычные блоки итераторов (т. Е. «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)
    }
}

Но снова, компилятор возвращает ошибку:"невозможно загрузить строку сообщения из ресурсов".


Вот реальный программный код в моем проекте

Это очень полезно, когда у меня есть задача списка, которую можно загрузить.HTML из URL, и я использую синтаксис «задача возврата доходности», в результате я хочу 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;
}

Но, похоже, я должен.

Спасибо за любую помощь.

Ответы [ 9 ]

67 голосов
/ 30 мая 2011

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();
}
66 голосов
/ 08 июля 2011

То, что вы описываете, может быть выполнено методом Task.WhenAll. Обратите внимание, как код превращается в простой однострочный. Происходит следующее: каждый отдельный URL-адрес начинает загрузку, а затем используется WhenAll, объединяя эти операции в один Task, который можно ожидать.

Task<IEnumerable<string>> DownLoadAllUrls(string[] urls)
{
    return Task.WhenAll(from url in urls select DownloadHtmlAsync(url));
}
21 голосов
/ 14 ноября 2018

В соответствии с новыми функциями в 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);
}

Ответ Брайана Гидеона подразумевает, чтовызывающий код будет асинхронно получать коллекцию результатов, которые были получены параллельно.Приведенный выше код подразумевает, что вызывающий код будет получать результаты наподобие потока один за другим асинхронно.

16 голосов
/ 25 февраля 2016

Я знаю, что слишком поздно с ответом, но вот еще одно простое решение, которое может быть достигнуто с помощью этой библиотеки: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 голосов
/ 13 ноября 2018

Эта функция будет доступна с 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;
}

Это не очень полезно, если вы хотите использовать (или создавать) непрерывные потоки результатов, например, которые вы можете получить с устройства IoT или облачной службы.Для этого существуют асинхронные потоки.

Мы представляем IAsyncEnumerable, который является именно тем, что вы ожидаете;асинхронная версия IEnumerable.Язык позволяет вам ожидать foreach над ними, чтобы использовать их элементы, и возвращать их для создания элементов.

async IAsyncEnumerable<int> GetBigResultsAsync()
{
    await foreach (var result in GetResultsAsync())
    {
        if (result > 20) yield return result; 
    }
}
4 голосов
/ 05 октября 2017

Был план сделать

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

Но в настоящее время это невозможно

1 голос
/ 03 октября 2017

Доходность не работает с await, к сожалению. Но это то, для чего Rx. Проверить https://msdn.microsoft.com/library/hh242985

1 голос
/ 22 февраля 2011

Прежде всего, имейте в виду, что Async не закончен. Команде C # еще предстоит пройти долгий путь до выхода C # 5.

При этом, я думаю, вы, возможно, захотите собрать задачи, выполняемые в функции 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();
0 голосов
/ 04 мая 2017

Это решение работает как ожидалось.Обратите внимание на 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;
        }
    }
}
...