Có thể "chờ đợi lợi nhuận trả về DoSomethingAsync ()"


108

Các khối trình lặp thông thường (tức là "trả về lợi nhuận") không tương thích với "async" và "await"?

Điều này cung cấp một ý tưởng tốt về những gì tôi đang cố gắng làm:

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

Tuy nhiên, tôi gặp lỗi trình biên dịch trích dẫn "không thể tải chuỗi thông báo từ tài nguyên".

Đây là một nỗ lực khác:

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

Nhưng một lần nữa, trình biên dịch trả về lỗi: "không thể tải chuỗi thông báo từ tài nguyên".


Đây là mã lập trình thực trong dự án của tôi

Điều này rất hữu ích khi tôi có một Nhiệm vụ danh sách, tác vụ đó có thể được tải xuống HTML từ một URL và tôi sử dụng cú pháp "tác vụ trả về chờ đợi", kết quả là tôi muốn IEnumerable<Foo>. Tôi không muốn viết mã này:

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

Nhưng có vẻ như tôi phải làm vậy.

Cảm ơn vì bất kì sự giúp đỡ.


4
Câu hỏi này không đặc biệt rõ ràng, bạn nên giải thích chính xác những gì bạn muốn làm - rất khó để xác định bạn muốn làm gì chỉ từ một mẫu mã.
Justin

3
Gói Ix_Experimental-Async NuGet bao gồm "bộ liệt kê không đồng bộ" hoàn chỉnh với hỗ trợ LINQ. Họ sử dụng IAsyncEnumerator<T>kiểu được xác định bởi Arne.
Stephen Cleary

@StephenCleary rằng gói đó đã bị xóa. Vì điều này đã có từ lâu và được tác giả bởi rxteam của MS, bạn có biết nếu nó được đưa vào RX không?
JoeBrockhaus

@JoeBrockhaus: Có vẻ như nó tồn tại dưới dạng Ix-Async .
Stephen Cleary

Câu trả lời:


72

Những gì bạn đang mô tả có thể được thực hiện bằng Task.WhenAllphương pháp này. Lưu ý cách mã chuyển thành một lớp lót đơn giản. Điều xảy ra là mỗi url riêng lẻ bắt đầu tải xuống và sau đó WhenAllđược sử dụng kết hợp các hoạt động đó thành một url duy nhất Taskcó thể được chờ đợi.

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

10
async / await không cần thiết trong trường hợp này. Chỉ cần xóa asynckhỏi phương thức và làm return Task.WhenAlltrực tiếp.
luiscubal

22
Dòng cuối cùng có thể được viết ngắn gọn hơn nhưurls.Select(DownloadHtmlAsync)
BlueRaja - Danny Pflughoeft

1
Đây hóa ra là vấn đề chính xác mà tôi đang gặp phải (tải các chuỗi từ một loạt URI một cách lười biếng). Cảm ơn bạn.
James Ko

13
Điều này không thực sự trả lời câu hỏi Is it possible to await yield? Bạn vừa tìm thấy một giải pháp cho một trường hợp sử dụng này. Không trả lời câu hỏi chung.
Buh Buh

1
Tôi sẽ có xu hướng trả lại Task<string[]>vì điều này sẽ chỉ ra rằng bạn không còn trả lại một trình lặp với việc thực thi hoãn lại tức là. tất cả các URL đang được tải xuống.
johnnycardy

73

tl; dr Các phần tử lặp khi được triển khai với lợi nhuận là một cấu trúc chặn, vì vậy ngay bây giờ đang chờ đợi và lợi nhuận không tương thích.

Long Bởi vì việc lặp qua an IEnumerablelà một thao tác chặn, việc gọi một phương thức được đánh dấu là asyncsẽ vẫn thực thi nó theo cách chặn, vì nó phải đợi thao tác đó kết thúc.

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

Sự chờ đợi Methodtrộn lẫn ý nghĩa. Bạn có muốn đợi cho đến khi Taskcó một IEnumerablekhối rồi lặp lại nó không? Hay bạn đang cố gắng chờ đợi từng giá trị của IEnumerable?

Tôi giả sử điều thứ hai là hành vi mong muốn và trong trường hợp đó, ngữ nghĩa của Iterator hiện có sẽ không hoạt động. Các IEnumerator<T>giao diện cơ bản là

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

Tôi đang bỏ qua Reset()vì nó không có ý nghĩa gì đối với một chuỗi kết quả không đồng bộ. Nhưng những gì bạn cần là một cái gì đó như thế này:

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

Tất nhiên, foreachcũng sẽ không hoạt động với điều này và bạn phải lặp lại theo cách thủ công như sau:

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
Bạn có nghĩ rằng có thể phiên bản ngôn ngữ C # tiếp theo sẽ foreachhỗ trợ thêm cho giả thuyết của bạn IAsyncEnumerator<T>không?
Đại

1
@Dai Tôi nghĩ rằng nó rất nhiều trong đường ống. cho C # 8.0 / Đã đọc về nó.
nawfal


40

Theo các tính năng mới tại C # 8.0 ( liên kết số 1liên kết số 2 ), chúng tôi sẽ IAsyncEnumerable<T>hỗ trợ giao diện cho phép bạn thực hiện lần thử thứ hai. Nó sẽ trông giống thế này:

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

Chúng ta có thể đạt được hành vi tương tự ở C # 5 nhưng với ngữ nghĩa khác:

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

Câu trả lời của Brian Gideon ngụ ý rằng mã gọi sẽ nhận được một tập hợp các kết quả thu được song song một cách không đồng bộ. Đoạn mã trên ngụ ý rằng mã gọi sẽ nhận được kết quả giống như từ một luồng một theo cách không đồng bộ.


17

Tôi biết rằng tôi đã quá muộn với câu trả lời, nhưng đây là một giải pháp đơn giản khác có thể đạt được với thư viện này:
GitHub: https://github.com/tyrotoxin/AsyncEnumerable
NuGet.org: https://www.nuget .org / package / AsyncEnumerator /
Nó đơn giản hơn nhiều so với 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

Tính năng này sẽ có sẵn kể từ C # 8.0. https://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/

Của MSDN

Luồng không đồng bộ

Tính năng async / await của C # 5.0 cho phép bạn sử dụng (và tạo ra) các kết quả không đồng bộ trong mã đơn giản mà không cần gọi lại:

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

Sẽ không hữu ích lắm nếu bạn muốn sử dụng (hoặc tạo ra) các luồng kết quả liên tục, chẳng hạn như bạn có thể nhận được từ thiết bị IoT hoặc dịch vụ đám mây. Các luồng không đồng bộ có sẵn cho điều đó.

Chúng tôi giới thiệu IAsyncEnumerable, chính xác là những gì bạn mong đợi; phiên bản không đồng bộ của IEnumerable. Ngôn ngữ cho phép bạn chờ đợi trước những điều này để tiêu thụ các phần tử của chúng và mang lại lợi nhuận cho chúng để tạo ra các phần tử.

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


1

Trước hết, hãy nhớ rằng nội dung Async vẫn chưa kết thúc. Nhóm C # vẫn còn một chặng đường dài trước khi C # 5 được phát hành.

Điều đó đang được nói, tôi nghĩ rằng bạn có thể muốn tập hợp các nhiệm vụ đang bị sa thải trong DownloadAllHtmlchức năng theo một cách khác.

Ví dụ: bạn có thể sử dụng một cái gì đó như sau:

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

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

Không phải là DownloadAllUrlhàm KHÔNG phải là một lệnh gọi không đồng bộ. Tuy nhiên, bạn có thể thực hiện lệnh gọi không đồng bộ trên một hàm khác (tức là DownloadHtmlAsync).

Thư viện song song nhiệm vụ có các chức năng .ContinueWhenAny.ContinueWhenAllchức năng.

Điều đó có thể được sử dụng như thế này:

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

Bạn cũng có thể có 'tiêu đề' (trước vòng lặp) nếu bạn xác định phương thức của mình là không đồng bộ Task<IEnumerable<Task<string>>> DownloadAllUrl. Hoặc, nếu bạn muốn các hành động 'footer' IEnumerable<Task>. Ví dụ: gist.github.com/1184435
Jonathan Dickinson,

1
Bây giờ là asyncthứ được hoàn thành và C # 5.0 được phát hành, điều này có thể được cập nhật.
casperOne 5/12/12

Tôi thích cái này, nhưng làm thế nào trong trường hợp này foreach (var url in await GetUrls ()) {return return GetContent (url)}; Tôi chỉ tìm thấy một cách tách trong hai phương pháp.
Juan Pablo Garcia Coello


-1

Giải pháp này hoạt động như mong đợi. Lưu ý await Task.Run(() => enumerator.MoveNext())phần.

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;
        }
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.