"Getiri dönüşü DoSomethingAsync ()" beklemek mümkün mü


108

Normal yineleme blokları (yani, "getiri dönüşü") "eşzamansız" ve "bekleme" ile uyumsuz mu?

Bu, yapmaya çalıştığım şey hakkında iyi bir fikir veriyor:

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

Ancak, "kaynaklardan ileti dizesi yüklenemedi" ifadesini belirten bir derleyici hatası alıyorum.

İşte başka bir girişim:

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

Ancak yine, derleyici bir hata döndürür: "kaynaklardan ileti dizesi yüklenemiyor".


İşte projemdeki gerçek programlama kodu

Bu, bir Liste Görevine sahip olduğumda, bu görev bir URL'den HTML indirilebildiğinde ve "getiri dönüş bekleme görevi" sözdizimini kullandığımda çok kullanışlıdır, sonuç istiyorum IEnumerable<Foo>. Bu kodu yazmak istemiyorum:

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

Ama görünüşe göre mecburum.

Herhangi bir yardım için teşekkürler.


4
Bu soru özellikle net değil, tam olarak ne yapmak istediğinizi detaylandırmalısınız - sadece bir kod örneğinden ne yapmak istediğinizi belirlemek zordur.
Justin

3
Ix_Experimental-zaman uyumsuz Nuget paketi "asenkron enumerator'ler" LINQ desteği ile komple içerir. IAsyncEnumerator<T>Arne tarafından tanımlanan türü kullanırlar .
Stephen Cleary

@StephenCleary bu paket listeden çıkarıldı. Bu yıllar önce olduğundan ve MS'in rxteam tarafından yazıldığından, bunun RX'e dahil edilip edilmediğini biliyor musunuz?
JoeBrockhaus

@JoeBrockhaus: Görünüşe göre Ix-Async olarak yaşıyor .
Stephen Cleary

Yanıtlar:


72

Tarif ettiğiniz şey Task.WhenAllyöntem ile başarılabilir . Kodun nasıl basit bir tek satıra dönüştüğüne dikkat edin. Olan şey, her bir url'nin indirilmeye başlaması ve daha sonra WhenAllkullanılması, bu işlemleri Taskbeklenebilecek tek bir işlemde birleştirmesidir .

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

10
bu durumda async / await gerekli değildir. Sadece asyncyöntemden çıkarın ve return Task.WhenAlldoğrudan yapın.
luiscubal

22
Son satır daha kısaca şu şekilde yazılabilirurls.Select(DownloadHtmlAsync)
BlueRaja - Danny Pflughoeft

1
Karşılaştığım tam sorun buydu (dizeleri bir dizi URI'den tembelce indirmek). Teşekkür ederim.
James Ko

13
Bu aslında Is it possible to await yield? bu tek kullanımlık durum için bir çözüm buldunuz sorusuna cevap vermiyor. Genel soruya cevap vermedi.
Buh Buh

1
Geri dönme eğilimindeyim, Task<string[]>çünkü bu artık ertelenmiş yürütme ile bir yineleyici döndürmediğinizi gösterir. tüm URL'ler indiriliyor.
johnnycardy

73

tl; dr Yineleyiciler getiri ile uygulandığı şekliyle engelleyici bir yapıdır, bu nedenle şu anda bekleyin ve verim uyumsuzdur.

Uzun Bir üzerinde yineleme IEnumerablebir engelleme işlemi olduğundan, olarak işaretlenmiş bir yöntemi çağırmak async, bu işlemin bitmesini beklemesi gerektiğinden, onu engelleyici bir şekilde yürütür.

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

Bekleyen Method, anlamları karıştırır. Eğer kadar beklemek istiyor musunuz Taskbir sahiptir IEnumerableve daha sonra üzerine yineleme üzerinde bloke? Yoksa her bir değerini beklemeye mi çalışıyorsunuz IEnumerable?

İkincisinin istenen davranış olduğunu varsayıyorum ve bu durumda mevcut Yineleyici semantiği çalışmayacaktır. IEnumerator<T>Arayüz temelde

public interface IEnumerator<T>
  T Current;
  bool MoveNext();
}

Reset()Bir dizi asenkron sonuç için bir anlam ifade etmediği için görmezden geliyorum . Ama ihtiyacınız olan şey şuna benzer:

public interface IAsyncEnumerator<T>
  T Current;
  Task<bool> MoveNext();
}

Tabii ki, foreachbununla da çalışmaz ve şu şekilde manuel olarak yinelemeniz gerekir:

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
Bir sonraki C # dili sürümünün foreachvarsayımınız için destek ekleyebileceğini düşünüyor musunuz IAsyncEnumerator<T>?
Dai

1
@Dai Bence boru hattında çok fazla. C # 8.0 için / Okudum.
nawfal


40

C # 8.0'daki yeni özelliklere göre ( bağlantı # 1 ve bağlantı # 2 ) IAsyncEnumerable<T>, ikinci denemenizi gerçekleştirmenize izin verecek arayüz desteğine sahip olacağız. Bunun gibi görünecek:

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

Aynı davranışı C # 5'te ancak farklı bir anlambilimle başarabiliriz :

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'un cevabı, çağıran kodun eşzamansız olarak paralel olarak elde edilen sonuçların bir koleksiyonunu alacağını ima eder. Yukarıdaki kod, çağıran kodun bir akıştan eşzamansız şekilde tek tek sonuçlar alacağı anlamına gelir.


17

Cevaba geç kaldığımı biliyorum, ancak işte bu kitaplıkla elde edilebilecek başka bir basit çözüm:
GitHub: https://github.com/tyrotoxin/AsyncEnumerable
NuGet.org: https: //www.nuget .org / packages / AsyncEnumerator / Rx'ten
çok daha basit.

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

Bu özellik C # 8.0'dan itibaren mevcut olacaktır. https://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/

MSDN'den

Zaman uyumsuz akışlar

C # 5.0'ın eşzamansız / bekleme özelliği, geri aramalar olmadan basit kodda eşzamansız sonuçları tüketmenizi (ve üretmenizi) sağlar:

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

Bir IoT cihazından veya bir bulut hizmetinden alabileceğiniz gibi sürekli sonuç akışlarını tüketmek (veya üretmek) istiyorsanız o kadar yararlı değildir. Zaman uyumsuz akışlar bunun için var.

Tam olarak beklediğiniz gibi IAsyncEnumerable'ı tanıtıyoruz; IEnumerable'ın eşzamansız bir sürümü. Dil, öğelerini tüketmek için bunların üzerinde her birini beklemenizi ve öğeleri üretmek için onlara geri dönmenizi sağlar.

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


1

Her şeyden önce, Async işinin bitmediğini unutmayın. C # ekibinin, C # 5 yayınlanmadan önce kat etmesi gereken uzun bir yol var.

Bununla birlikte, DownloadAllHtmlişlevden atılan görevleri farklı bir şekilde toplamak isteyebileceğinizi düşünüyorum .

Örneğin, bunun gibi bir şey kullanabilirsiniz:

IEnumerable<Task<string>> DownloadAllUrl(string[] urls)
{
    foreach(var url in urls)
    {
        yield return DownloadHtmlAsync(url);
    }
}

async Task<string> DownloadHtmlAsync(url)
{
    // Do your downloading here...
}

Değil o DownloadAllUrlfonksiyonudur DEĞİL bir zaman uyumsuz çağrı. Ancak, zaman uyumsuz çağrının başka bir işlevde (yani DownloadHtmlAsync) uygulanmasını sağlayabilirsiniz .

Görev Paralel Kitaplığı, .ContinueWhenAnyve .ContinueWhenAllişlevlerine sahiptir.

Bu şu şekilde kullanılabilir:

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

Yönteminizi eşzamansız olarak tanımlarsanız, bir 'başlığa' (döngüden önce) sahip olabilirsiniz Task<IEnumerable<Task<string>>> DownloadAllUrl. Veya 'altbilgi' eylemleri istiyorsanız IEnumerable<Task>. Ör. Gist.github.com/1184435
Jonathan Dickinson

1
Şimdi bu asyncşeyler olduğunu bitmiş ve C # 5.0 edilir yayımlanan bu olabilir güncellenebilir.
casperOne

Bunu beğendim, ama bu durumda foreach (var url in await GetUrls ()) {give return GetContent (url)}; Sadece iki yöntemi ayırmanın bir yolunu buldum.
Juan Pablo Garcia Coello


-1

Bu çözüm beklendiği gibi çalışıyor. Parçayı not edin 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;
        }
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.