4

I'm sure this question is going to prove my ignorance, but I'm having a hard time understanding this. I'm willing to ask a dumb question to get a good answer.

All of the posts I've read about async streams do a good job of showing off the feature, but they don't explain why it's an improvement over the alternative.

Or, perhaps, when should one use async streams over good old client-server communication?

I can see where streaming the contents of a large file might be a good use for async streams, but many of the examples I've seen use async streams to transmit small bits of sensor data (temperature, for example). It seems like an IoT device with a temperature sensor could just HTTP POST the data to a server, and the server could respond. Why would the server implement async streams in that case?

I can already feel your pain as you struggle to make sense of those words, but please have mercy on me. :)

As requested, here are some examples I've come across that confused me. I'll post more as I find them, but I wanted to go ahead and get you started:

Panagiotis Kanavos
  • 90,087
  • 9
  • 138
  • 171
Alex Dresko
  • 4,817
  • 2
  • 35
  • 54
  • 1
    The two aren't related *at all*. Async streams are a programming concept that has nothing to do with networking. That programming concept though can be used to make gRPC streams easier to write – Panagiotis Kanavos Oct 16 '19 at 12:07
  • Maybe you could give a reference to some of the posts you've seen, and tell us what you understand them to say, and then we can try and identify any points of confusion. These are really complimentary concepts, not competing concepts. [Here's an MSDN tutorial](https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/generate-consume-asynchronous-stream) on the feature; notice how the implementation of the Async stream itself uses "good old client-server communication" insomuch as it uses a standard HTTP Post – chill94 Oct 16 '19 at 12:13
  • I've updated the question to include a few of the more recent posts I've come across. I'll try to find more... But, then, I guess maybe the real question is, "When are async streams appropriate"? The example @chill94 gave here is helpful, I think... Is the driving factor that async streams are "pull based" and that they allow intermediate results with cancellation? Obviously, I'm still trying to organize the thoughts in my head and formulate the question. When I started this, I really wasn't sure what to ask, so these comments are helping me. Thanks. – Alex Dresko Oct 16 '19 at 12:33
  • 1
    @AlexDresko no, the two things are *unrelated*. Async streams is `await foreach` and IAsyncEnumerable. Nothing at all to do with IO, HTTP and networking. They can be used to make calling gRPC streams easier – Panagiotis Kanavos Oct 16 '19 at 12:42
  • I will say, (and maybe this is a language thing more than anything else) calling them “unrelated” is maybe a bit strong. I would say the “++” operator and “for” loops are related, because I see them often in the same context. Likewise here. I don’t say this to criticize - your answer is spot on - but it may help clarify your point to future readers. *These concepts are related insomuch as the are often seen in the same context.* – chill94 Oct 16 '19 at 15:47

1 Answers1

7

I wanted to write a professional response but the crude one is probably needed too:

Forget you ever heard about async streams. What were they thinking?

Call it await foreach, or async enumerables or async iterators. It has nothing to do with IO and streams.

The term is used because it exists in other languages, not because it has anything to do with IO. In Java for example, streams are Java's implementation of C#'s IEnumerable. So, to ease adoption by future Android devs, C# adopted Java's bad idea.

We can look at the language design meetings for the actual justification for this term I guess.


Serious original answer

There's no vs. It's like contrasting automatic gear boxes and cars. Cars can have automatic gear boxes, they aren't used instead of gear boxes.

Async streams is purely a programming concept that allows the creation of async iteratos. It's the feature that allows us to write this to make HTTP calls in a loop and process the results as they arrive :

await foreach(var someValue from someAsyncIterator(5))
{
    ...
}

IAsyncEnumerable<string> someAsyncIterator(int max)
{
    for(int i=0;i<max;i++)
    {
        var response=await httpClient.GetStringAsync($"{baseUrl}/{i}");
        yield return response;
    }
}

When they appear as action results it's only to allow the ASP.NET Core middleware to start processing results as they are produced, they don't affect the contents of the HTTP response itself.

gRPC's streams on the other hand allow the server to send individual responses to the client asynchronously. Laurent Kempe in gRPC and C# 8 Async stream and Steve Gordon in Server Streaming with GRPC and .NET Core show how these can be used together

Copying from Steve Gordon's samples, let's say we have a weather service that sends forecasts to the client, whose proto file contains :

service WeatherForecasts {
  rpc GetWeather (google.protobuf.Empty) returns (WeatherReply);
  rpc GetWeatherStream (google.protobuf.Empty) returns (stream WeatherData);
  rpc GetTownWeatherStream (stream TownWeatherRequest) returns (stream TownWeatherForecast);
}

Before C# 8, the client would have to block until it received all responses before processing them:

using var channel = GrpcChannel.ForAddress("https://localhost:5005");
var client = new WeatherForecastsClient(channel);
var reply = await client.GetWeatherAsync(new Empty());
foreach (var forecast in reply.WeatherData)
{
        //Do something with the data
}

In C# 8 though, the responses can be received and processed as they arrive :

using var replies = client.GetWeatherStream(new Empty(), cancellationToken: cts.Token);

await foreach (var weatherData in replies.ResponseStream.ReadAllAsync(cancellationToken: cts.Token))
{
        //Do something with the data
}

**

Panagiotis Kanavos
  • 90,087
  • 9
  • 138
  • 171
  • `HttpClient.GetStringAsync` returns a `Task`, so that example doesn't seem right. And the weather example is precisely what's confusing. Weather data streams never end, because weather never ends. There's no such thing as "blocking until it receives all responses" because the end never comes. Before C#8, a client would likely poll the server, or the server would push weather data to the client. Hence my notion that async streams are for "pull based" situations. Am I wrong? – Alex Dresko Oct 16 '19 at 12:51
  • @AlexDresko `await httpClient.GetStringAsync($"{baseUrl}/{i}")` produces a string. As for the weather example, it's about *this particualr sample code* which returns only a few dozen responses with an artificial delay. There *is* an end to the calls. – Panagiotis Kanavos Oct 16 '19 at 12:52
  • 2
    @AlexDresko just forget you ever heard about `async streams` and think about `await foreach`. I have to make an effort myself to call this feature `async streams`. The term is used because it exists in other languages, not because it has anything to do with IO. In Java for example, streams are push-based iterators when .NET's IEnumerable is pull-based. And now forget about push- vs pull- as it has nothing to do with gRPC – Panagiotis Kanavos Oct 16 '19 at 12:55
  • Your last comment is a better answer than your actual answer which still suffers from the points I brought up in my first comment https://stackoverflow.com/questions/58412850/c-sharp-8-async-streams-vs-rest-rpc?noredirect=1#comment103170440_58413589. Unfortunately, I cannot reward this answer until it negates the very confusion I came here with. – Alex Dresko Oct 17 '19 at 12:45
  • @AlexDresko Please explain why you think that the `Task` return type from `HttpClient` makes the example wrong. – cremor Oct 18 '19 at 05:07
  • @cremor the problem is the `async streams` term, not the actual example.When people read `stream` they take it to mean `IO`, which has nothing to do with `await foreach`. The C# team picked the term used by Java for its own implementation of push-style IEnumerable. Java called it `Streams` so the C# team, probably interested in interop in the Android world, also used `streams` for the new feature. If that sounds strange, the primary motivation for default interface members was interop with the default members used in the Android SDK – Panagiotis Kanavos Oct 18 '19 at 07:17
  • @cremor in short *what were they thinking* when they called this async streams? – Panagiotis Kanavos Oct 18 '19 at 07:29
  • @cremor For one thing, it doesn't even compile. `httpClient.GetStringAsync` returns a `Task`, not `IAsyncEnumerable`. Unless I'm completely stupid, his `for` loop is the equivalent of `await foreach (var strange in Task.FromResult("some string")) {}` Also, his `for` loop doesn't appear to do anything meaningful with `i`. – Alex Dresko Oct 18 '19 at 12:25
  • 1
    @AlexDresko what code are you talking about? `await client.GetStringAsync()` returns a single value. It's *yield return* that returns that value in the iterator immediatelly. As for `i`, that's part of the URL. I was trying to show the difference between the concepts and show that they have *nothing* in common, not write a full example, or a tutorial. You've already linked to a very good tutorial, the same one I started with – Panagiotis Kanavos Oct 18 '19 at 12:31
  • 1
    @AlexDresko `Task.FromResult` doesn't run anything either, it returns a completed task. `GetStringAsync()` on the other hand runs asynchronously and is *awaited* by `await` without blocking the caller. That's what `async/await` is about. In turn the `IAsyncEnumerable` *await* for values without blocking. You couldn't do that in the past – Panagiotis Kanavos Oct 18 '19 at 12:33
  • Oh, I see now, you're right. The confusing thing, then, is that it's a for loop hard coded to return a single value. Not sure why anyone would use that as an example when trying to make something clear. – Alex Dresko Oct 18 '19 at 12:34
  • @AlexDresko because I was writing an answer for a different question - what's the difference between async streams and gRPC streams, not trying to write a tutorial or even a blog post. Christian Nagel already wrote a very good tutorial on async streams – Panagiotis Kanavos Oct 18 '19 at 12:37
  • I will accept this answer now since I believe there are enough comments here that someone can infer a complete answer, even if it requires making sense of our back-and-forth. I did run across [this](https://stackoverflow.com/a/47214496/250094) answer last night, which I think assists with _why/when_ one would chose async streams over the alternatives. It does seem to suggest that my "pull based" assertion was correct. Thank you for taking time to help me understand all of this. I do understand now, and it was extremely helpful being able to talk it out. We just had to get on the same page. – Alex Dresko Oct 18 '19 at 12:41
  • `When they appear as action results it's only to allow the ASP.NET Core middleware to start processing results as they are produced, they don't affect the contents of the HTTP response itself.` This was an extremely useful statement, I hadn't found it clearly expressed anywhere else. +1 – Chris Jan 10 '20 at 15:32