Возможно ли "ожидание возврата доходности DoSomethingAsync()"

Являются ли регулярные блоки итератора (т.е. "доходность возврата" ) несовместимыми с "асинхронными" и "ожидающими"?

Это дает хорошее представление о том, что я пытаюсь сделать:

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 html code 
              );
     return finalResult;
}

Однако, я получаю ошибку компилятора, ссылаясь на "невозможно загрузить строку сообщения из ресурсов".

Вот еще одна попытка:

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

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


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

Это очень полезно, когда у меня есть задача списка, эта задача может загружать HTML из URL-адреса и я использую синтаксис "return return wait 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;
}

Но мне кажется, что я должен.

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

Ответ 1

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

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

Ответ 2

tl; dr Итераторы, реализованные с выходом, представляют собой блокирующую конструкцию, поэтому прямо сейчас ожидание и выход несовместимы.

Длинный. Поскольку итерация по 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();
}

Ответ 3

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

Мы можем добиться того же поведения в С# 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);
}

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

Ответ 4

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

Ответ 5

Эта функция будет доступна с С# 8.0. https://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/

Из MSDN

Асинхронные потоки

Функция асинхронного ожидания/ожидания в С# 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; 
    }
}

Ответ 7

Прежде всего, имейте в виду, что материал Async не завершен. Команде С# еще предстоит пройти долгий путь до выпуска С# 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();

Ответ 9

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