Description
I realize this project is probably somewhat abandoned, but I've just discovered it and am hoping to use it in a legacy .NetFramework scenario. It seems to offer a simple (and working) implementation of GRPC over NamedPipes, ideal for client<->server IPC on the local machine.
However, in my initial prototyping I've ran into a problem.
I have a server that exposes a streaming operation the sends status updates to the client; the client listens and processes the status updates.
I'm trying to make the client resilient, so that if the server goes away, the client will periodically attempt to reconnect and reestablish the status listening.
However the IAsyncEnumerable implementation that the client uses to listen for the server updates does not return if the server connection ends unexpectedly. Essentially this means the client doesn't realise the server has gone - and it just waits endlessly.
Server.cs
var server = new LiteServer();
server.ServiceBinder.Bind(new MyService());
_ = _server.ListenAsync(ConnectionFactory.ListenNamedPipe("MY_NAMED_PIPE"));
MyService.cs
public class MyService
{
public IAsyncEnumerable<DeviceStatusEvent> SubscribeStatusAsync(CallContext context = default)
{
return SubscribeToStatusEvents(context.CancellationToken);
}
private async IAsyncEnumerable<StatusEvent> SubscribeToStatusEvents([EnumeratorCancellation] CancellationToken cancellationToken)
{
// Setup continuous publishing of any status events
while (!cancellationToken.IsCancellationRequested)
{
DeviceStatusEvent statusEvent;
try
{
statusEvent = await queue.Reader.ReadAsync(cancellationToken);
}
catch (OperationCanceledException e) when (cancellationToken.IsCancellationRequested)
{
break;
}
yield return statusEvent;
}
}
}
Client.cs
public async Task ListenForServerUpdatesAsync(CancellationToken shutdownToken)
{
while (true)
{
if (shutdownToken.IsCancellationRequested)
break;
try
{
using var channel = await ConnectionFactory.ConnectNamedPipe("MY_NAMED_PIPE").CreateChannelAsync(shutdownToken);
var service = channel.CreateGrpcService<IMyService>();
var statusEvents = service.SubscribeStatusAsync();
await foreach (var statusEvent in statusEvents)
{
// Process statusEvent
}
}
catch (OperationCanceledException) when (shutdownToken.IsCancellationRequested)
{
break;
}
catch (Exception)
{
}
await Task.Delay(TimeSpan.FromSeconds(10), shutdownToken);
}
}
If the process hosting the server is terminated, the async foreach
in the client blocks indefinitely.
Activity