10

When working with gRPC in C#, asynchronous calls return AsyncUnaryCall<T> (for unary calls - of course, other calls have slightly different return types). However, AsyncUnaryCall<T> does not extend Task<T>. Therefore, common things you would ordinarily do with a Task<T> do not work with AsyncUnaryCall<T>. This includes:

  • specifying the continuation policy (using ConfigureAwait)
  • using helpers like Task.WhenAny and Task.WhenAll

The latter is biting me at the moment, since I want to kick off multiple gRPC calls and wait for them all to complete. It seems my only recourse is to write a little helper that awaits for one after the other.

Why doesn't AsyncUnaryCall<T> mirror the functionality in Task<T>?

me--
  • 1,978
  • 1
  • 22
  • 42
  • 1
    It [looks like](https://github.com/grpc/grpc/blob/master/src/csharp/Grpc.Core/AsyncUnaryCall.cs) it exposes separate `Task`s for it's headers and the full response (see `ResponseAsync` and `ResponseHeadersAsync`). Why aren't those sufficient? – Damien_The_Unbeliever Feb 14 '19 at 07:00
  • 2
    You not necessarily need Task to apply await stuff, the type only needs to implement awaitable/awaiter pattern which AsyncUnaryCall does. – Dmytro Mukalov Feb 14 '19 at 08:30
  • You can pass in `call.ResponseAsync`. Or, for several: `await Task.WhenAll(calls.Select(x => x.ResponseAsync));` – Stephen Cleary Feb 14 '19 at 13:31
  • Nice, somehow I missed that when spelunking the API. Thanks. – me-- Feb 15 '19 at 00:01
  • One thing that's still no clear to me is whether I should be dereferencing `ResponseAsync` for every call I make so that I can `ConfigureAwait(false)` on it. – me-- Feb 15 '19 at 00:19

1 Answers1

12

As I said in a comment, whilst it's "Task-like", it actually represents two separate Tasks. If you want to work with the individual Tasks as Tasks, just access the appropriate property (e.g. ResponseHeadersAsync or ResponseAsync).

If you have a variable themAll of type List<AsyncUnaryCall<T>> then using WhenAll/WhenAny is easy:

await Task.WhenAny(themAll.Select(c=>c.ResponseHeadersAsync));

if you've got useful work you can do when any headers arrive, or

await Task.WhenAll(themAll.Select(c=>c.ResponseAsync));

if you can't do anything useful until they're all completed. As two examples. Similarly, you can extract one of these tasks and use it in an await with a ConfigureAwait, if you want to do so.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
  • I'm interested in exploring the `WhenAny` of numerious gRPC streams. Is there a reliable example of handling multiple streams in one thread where whichever stream produces data is handled as soon as possible? In addition to that, what happens to the state of the other streams after returning from `Task.WhenAny(...)` – Aaron Hudon Jan 16 '23 at 18:40