"Await yield return DoSomethingAsync ()"가 가능합니까?


108

일반 반복자 블록 (예 : "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>. 결과는 I want 입니다. 이 코드를 작성하고 싶지 않습니다.

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

하지만해야 할 것 같습니다.

도움을 주셔서 감사합니다.


4
이 질문은 특히 명확하지 않습니다. 정확히 무엇을하고 싶은지 자세히 설명해야합니다. 코드 샘플만으로는 무엇을하고 싶은지 결정하기가 어렵습니다.
저스틴

3
Ix_Experimental - 비동기 NuGet 패키지는 "비동기 열거"LINQ 지원 완료가 포함되어 있습니다. 그들은 IAsyncEnumerator<T>Arne이 정의한 유형을 사용합니다 .
Stephen Cleary 2011 년

@StephenCleary 패키지가 목록에서 삭제되었습니다. 이것은 오래 전이고 MS의 rxteam에 의해 작성되었으므로 RX에 포함되었는지 알고 있습니까?
JoeBrockhaus

@JoeBrockhaus : Ix-Async로 살아있는 것 같습니다 .
Stephen Cleary

답변:


72

설명하는 내용은 Task.WhenAll방법 으로 수행 할 수 있습니다 . 코드가 어떻게 단순한 한 줄로 변하는 지 주목하십시오. 발생하는 것은 각 개별 URL이 다운로드를 시작한 다음 WhenAll이러한 작업을 Task기다릴 수 있는 단일로 결합하는 데 사용됩니다 .

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

10
이 경우 async / await는 필요하지 않습니다. async방법에서 제거 하고 return Task.WhenAll직접 수행하십시오.
luiscubal

22
마지막 줄은 더 간결과 같이 쓸 수있다urls.Select(DownloadHtmlAsync)
대니 Pflughoeft - BlueRaja

1
이것은 내가 직면 한 정확한 문제로 밝혀졌습니다 (일련의 URI에서 문자열을 느리게 다운로드). 감사합니다.
James Ko

13
이것은 실제로이 Is it possible to await yield? 사용 사례에 대한 해결 방법을 찾았다 는 질문에 실제로 대답하지 않습니다 . 일반적인 질문에 대답하지 않았습니다.
Buh Buh

1
Task<string[]>이것은 지연된 실행 즉, 더 이상 반복자를 반환하지 않는다는 것을 나타내므로 반환 하는 경향이 있습니다 . 모든 URL이 다운로드되고 있습니다.
johnnycardy

73

tl; dr yield로 구현 된 반복자는 차단 구조이므로 지금은 대기하고 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();
}

1
다음 C # 언어 버전이 foreach귀하의 가상에 대한 지원을 추가 할 가능성이 있다고 생각하십니까 IAsyncEnumerator<T>?
다이

1
@Dai 나는 그것이 파이프 라인에서 매우 많이 생각합니다. C # 8.0 /에 대해 읽었습니다.
nawfal


40

C # 8.0 의 새로운 기능 ( link # 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의 대답은 호출 코드가 병렬로 얻은 결과 모음을 비동기 적으로 가져올 것임을 의미합니다. 위의 코드는 호출 코드가 비동기 방식으로 하나씩 스트림에서 결과를 얻을 수 있음을 의미합니다.


17

답변에 너무 늦었다는 것을 알고 있지만 여기에이 라이브러리로 얻을 수있는 또 다른 간단한 솔루션이 있습니다.
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

이 기능은 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의 비동기 버전입니다. 언어를 사용하면 foreach가 요소를 소비하고 요소를 생성하기 위해 반환을 기다릴 수 있습니다.

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


1

우선, 비동기 작업이 완료되지 않았 음을 명심하십시오. 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();

메서드를 async로 정의하는 경우 '헤더'(루프 앞)를 가질 수도 있습니다 Task<IEnumerable<Task<string>>> DownloadAllUrl. 또는 '바닥 글'작업을 원하는 경우 IEnumerable<Task>. 예 : gist.github.com/1184435
Jonathan Dickinson

1
이제 async작업 완료되고 C # 5.0 출시 되었으므로 업데이트 할 수 있습니다.
casperOne

나는 이것을 좋아하지만,이 경우 어떻게 foreach (var url in await GetUrls ()) {yield return GetContent (url)}; 나는 두 가지 방법으로 나뉘는 방법을 찾았습니다.
후안 파블로 가르시아 Coello


-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;
        }
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.