aspnetcore: [Epic]: Support returning values from client invocations
Support server to clients acks so that reliable messaging can be implemented more easily. This would only be when using Clients.Client(). I think we should go back to the SendAsync (fire and forget) InvokeAsync (wait for ack) naming pattern. That’s the one sticking point.
EDIT by @anurse: For clarity, this issue is tracking all work related to allowing values to be returned from client invocations. It also covers allowing the server to wait for a void-returning (or Task-returning) client side method to complete.
Work left for .NET 7
- #41777
- What does an async
.Onmethod look like? ProbablySingle<T>
- What does an async
- #41996
This was already an issue with Task returning.Onmethods, but client results likely makes it more likely to block on the client side[ ] #41997- Today we detect if you allow parallel hub invocations and throw if you don’t when trying to use the feature. This doesn’t work if you use
IHubContextin the Hub, or if you have multiple waiting results for the same connections Hubs. - This is also especially bad in
OnConnectedAsyncbecause that’s a special method that runs before the receive loop starts, we need to throw/unblock/warn etc. for this[ ] Analyzer to warn about strongly-typed hubs and usingInvokeAsyncwith.All,.Group, etc.[ ]InvokeAsyncvoid result? Scenario, acks without needing a value[ ] [Scaleout] ServerA requests client result from connection on ServerB, ServerB goes down after receiving request, ServerA needs to know somehow so it can error the client result
- Today we detect if you allow parallel hub invocations and throw if you don’t when trying to use the feature. This doesn’t work if you use
- [Scaleout] Consider alternative unique invocation ID generation instead of prepending the connectionID (https://github.com/dotnet/aspnetcore/pull/40811#discussion_r845643262)
[ ] Look at performanceThe biggest performance issue I can think of right now is thatRawResultallocates and copy the bytes which can be expensive[ ] Flow cancellation from server to client- InjectCancellationTokeninto.Onmethods and sendCancelInvocationhub messages to clients
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Reactions: 23
- Comments: 66 (36 by maintainers)
I have the same need
@brettclutch Wouldn’t the following do exactly what you want?
another +1 on this
Done. #44014
The initial iteration of client return results is out and will be in preview4 bits.
Users
To use this new feature there are changes you need to make on the server side and the client side. On the server you need to use the new APIs on IHubContext or IHubCallerClients.
or inside a Hub method, the caveat here is that you need to set the
MaximumParallelInvocationsPerClientoption on the server to be greater than 1, and there are still some issues with this that we’re working through in later previews:Additionally, strongly-typed hubs can be used so now your interfaces can return a value and be used:
And on the client you need to return a value from your
.On(...)method: .NET Client:TypeScript Client:
HubLifetimeManager implementors
Three new methods have been added to HubLifetimeManager, they have default implementations (that throw NotImplementedExcetion) so that it’s not a breaking change to consume the new bits. Additionally, if the implementation of HubLifetimeManager communicates with other servers then you will need to be aware of the
RawResulttype which is used to pass the raw serialized bytes of a clients payload to another server without needed to fully deserialize the payload on the intermediary server.IHubProtocol implementors
The main change needed for
IHubProtocolis to handle theRawResulttype.IInvocationBinder.GetReturnType(string invocationID)can returnRawResultwhich means don’t deserialize the result and instead store the raw bytes of the result in theRawResulttype. And when serializing the payload if aRawResulttype is being written write the raw bytes as if they were already serialized.Example: Receiving Json of
{"type":3,"result":34,"invocationId":"1234"}and seeing that result isRawResultwill mean don’t try to deserialize 34 as an int or anything and instead store the bytes[(byte)'3', (byte)'4']in aRawResultobject. And when writing the Json of thisRawResultobject you wouldn’t serialize thebyte[]as that would base64 encode the value, you instead directly write(byte)'3'and(byte)'4'to the output.Work left for .NET 7
.Onmethod look like? ProbablySingle<T>HubConnection.Onw/return results blocks client receive pipeline.Onmethods, but client results likely makes it more likely to block on the client side.Onhandlers but does not block the receive loop.Onhandlers for future invokes/sends can still run while blocked on user input, pings/etc. still received and processedInvokeAsyncin Hub methods can soft-lock the connectionIHubContextin the Hub, or if you have multiple waiting results for the same connections Hubs.OnConnectedAsyncbecause that’s a special method that runs before the receive loop starts, we need to throw/unblock/warn etc. for thisInvokeAsyncwith.All,.Group, etc.InvokeAsyncvoid result? Scenario, acks without needing a valueRawResultallocates and copy the bytes which can be expensiveCancellationTokeninto.Onmethods and sendCancelInvocationhub messages to clientsYep, we’ll either document it somewhere or see if we can make it error. Added it to the list above.
Ah yeah, thought about that scenario but didn’t look into it. It’s not going to work and likely never will work because of how OnConnectedAsync is called.
I like how easy they simply say “no plan to do it” when that “it” is mandatory feature even for a software 20 years ago.
another +1 on this
I use signalr to maintain proxy connections and much like @DavidErben I too could eliminate a lot of code required to direct responses.
Client connects to the master node and says I want this web address rendered this way the task is completed results put into response db queue then sent back to the client. It’s not bad but a lot of code could be eliminated if it was as simple as a client invocation.
Any news ? Is that really in. Net 5? Have you Any documentation link ?
Hey guys, I just tried this out with .NET 7 RC1 and the methods are still blocking (like before if one returned a Task). Are there any plans to change this behavior for the official .NET 7 release?
We are using this feature to trigger commands on connected IoT devices. Usually a bunch of commands are triggered simultaneously, so it would be cool if they would run in parallel. But if it is not possible it is not a huge deal either.
cc @vicancy
Hey David,
we are using SignalR to manage a network of about 1500 IoT nodes (Raspberry Pis). We are invoking methods on the clients to read logs or configuration files, upload data or trigger commands. Right now we use an asynchronous approach so that we create a task object and store it in a database and then send it to the client. The client executes it and sends the response to an API endpoint which sets the status of the task to finished. Currently I am updating the server so that the client directly responds on the SignalR connection, but I need to manually sync tasks/threads to return the response within the same HTTP request (for the caller of my API). It works, but it is a bit clunky.
With return values it would be super easy for us to directly invoke commands on a client node and immediately receive the response. So definitely a +1 from my side for this enhancement.
Thanks for contacting us.
We’re moving this issue to the
.NET 7 Planningmilestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s). If we later determine, that the issue has no community involvement, or it’s very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.There’s no news, this feature hasn’t been implemented
It works in 5.0.0.
another +1 on this