เป็นไปได้หรือไม่ที่จะ“ รอผลตอบแทน DoSomethingAsync ()”


108

บล็อกตัวทำซ้ำปกติ (เช่น "ผลตอบแทน") ไม่เข้ากันกับ "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;
}

แต่ดูเหมือนว่าฉันจะต้อง

ขอบคุณสำหรับความช่วยเหลือ


4
คำถามนี้ไม่ชัดเจนโดยเฉพาะอย่างยิ่งคุณควรอธิบายอย่างละเอียดว่าคุณต้องการทำอะไร - เป็นการยากที่จะระบุว่าคุณต้องการทำอะไรจากตัวอย่างโค้ดเพียงอย่างเดียว
Justin

3
แพคเกจ Ix_Experimental-Async NuGetรวมถึง "enumerators ตรงกัน" พร้อมด้วยการสนับสนุน LINQ พวกเขาใช้IAsyncEnumerator<T>ประเภทตามที่ Arne กำหนด
Stephen Cleary

@StephenCleary แพ็กเกจนั้นถูกลบออก เนื่องจากเป็นยุคที่ผ่านมาและประพันธ์โดย rxteam ของ MS คุณรู้หรือไม่ว่ามันถูกนำไปใช้ 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)
BlueRaja - Danny Pflughoeft

1
สิ่งนี้กลายเป็นปัญหาที่ฉันเผชิญอยู่ (ดาวน์โหลดสตริงจากชุด URI อย่างเกียจคร้าน) ขอบคุณ.
James Ko

13
สิ่งนี้ไม่ได้ตอบคำถามที่ว่าIs it possible to await yield? คุณเพิ่งพบวิธีแก้ปัญหาสำหรับกรณีการใช้งานนี้ ไม่ได้ตอบคำถามทั่วไป
Buh Buh

1
ฉันมีแนวโน้มที่จะกลับมาTask<string[]>เนื่องจากจะแสดงว่าคุณไม่ได้ส่งคืนตัววนซ้ำด้วยการดำเนินการที่รอการตัดบัญชีอีกต่อไป กำลังดาวน์โหลด URL ทั้งหมด
johnnycardy

73

tl; dr Iterators ตามที่นำไปใช้กับผลตอบแทนเป็นโครงสร้างการปิดกั้นดังนั้นในขณะนี้กำลังรอและผลตอบแทนไม่เข้ากัน

Longเนื่องจากการทำซ้ำบน an 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();
}

1
คุณคิดว่ามันเป็นไปได้ว่า C # รุ่นภาษาต่อไปจะเพิ่มforeachการสนับสนุนสำหรับการสมมุติของคุณIAsyncEnumerator<T>?
ได

1
@ ไดฉันคิดว่ามันมากในท่อ สำหรับ C # 8.0 / เคยอ่านมาแล้ว
nawfal


40

ตามคุณสมบัติใหม่ที่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 หมายความว่ารหัสการโทรจะได้รับชุดของผลลัพธ์ที่ได้รับแบบคู่ขนานแบบอะซิงโครนัส รหัสด้านบนหมายความว่ารหัสการโทรจะได้รับผลลัพธ์เหมือนจากสตรีมทีละรายการในลักษณะอะซิงโครนัส


17

ฉันรู้ว่าฉันตอบช้าเกินไป แต่นี่เป็นอีกวิธีง่ายๆที่สามารถทำได้กับไลบรารีนี้:
GitHub: https://github.com/tyrotoxin/AsyncEnumerable
NuGet.org: https: //www.nuget .org / package / 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

สตรีม Async

คุณลักษณะ async / await ของ C # 5.0 ช่วยให้คุณใช้ (และสร้าง) ผลลัพธ์แบบอะซิงโครนัสในรหัสที่ตรงไปตรงมาโดยไม่ต้องเรียกกลับ:

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

จะไม่เป็นประโยชน์หากคุณต้องการใช้ (หรือสร้าง) สตรีมผลลัพธ์อย่างต่อเนื่องเช่นคุณอาจได้รับจากอุปกรณ์ IoT หรือบริการคลาวด์ สตรีม Async อยู่ที่นั่น

เราขอแนะนำ IAsyncEnumerable ซึ่งเป็นสิ่งที่คุณคาดหวัง IEnumerable เวอร์ชันอะซิงโครนัส ภาษาช่วยให้คุณสามารถรอคอยสิ่งเหล่านี้เพื่อใช้องค์ประกอบของมันและให้ผลตอบแทนกลับมาเพื่อสร้างองค์ประกอบ

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


1

ก่อนอื่นโปรดทราบว่าสิ่งที่ 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ฟังก์ชั่นไม่ได้เป็นการเรียกแบบ async แต่คุณสามารถใช้การเรียก async กับฟังก์ชันอื่นได้ (เช่นDownloadHtmlAsync)

Task Parallel Library มีฟังก์ชัน.ContinueWhenAnyและ.ContinueWhenAll

ที่สามารถใช้ได้ดังนี้:

var tasks = DownloadAllUrl(...);
var tasksArray = tasks.ToArray();
var continuation = Task.Factory.ContinueWhenAll(tasksArray, completedTasks =>
{
    completedtask
});
continuation.RunSynchronously();

นอกจากนี้คุณยังสามารถมี 'หัว' (ก่อนที่วง) ถ้าคุณกำหนดวิธีการของคุณเป็น Task<IEnumerable<Task<string>>> DownloadAllUrlasync หรือถ้าคุณต้องการ IEnumerable<Task>'ส่วนท้ายของการกระทำ เช่นgist.github.com/1184435
Jonathan Dickinson

1
ตอนนี้ว่าasyncสิ่งที่เป็นสำเร็จรูปและ C # 5.0 ถูกปล่อยออกมานี้สามารถได้รับการปรับปรุง
casperOne

ฉันชอบอันนี้ แต่ในกรณีนี้ foreach (var url ในรอ GetUrls ()) {yield return GetContent (url)}; ฉันพบวิธีที่แยกออกเป็นสองวิธีเท่านั้น
Juan Pablo Garcia 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.