dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.48k stars 10.04k forks source link

[SignalR] Possibility to cancel long running hub method from client #11542

Open ddweber opened 5 years ago

ddweber commented 5 years ago

I´m not quit sure if this post is more of a question or a feature request.

I noticed that the InvokeAsync<TResult>(HubConnection, String, CancellationToken) method accepts a CancellationToken. As far as I understood this token only cancels the invocation (and waiting for a result, ...) on the client, while on the server side the method proceeds. Is this correct?

If so wouldn´t it be a nice feature to allow such a cancellation? I saw that Google offers this possibility in their gRPC framework aswell:

Cancelling RPCs Either the client or the server can cancel an RPC at any time. A cancellation terminates the RPC immediately so that no further work is done. It is not an “undo”: changes made before the cancellation will not be rolled back.

When my understanding is wrong please provide me an example on how to do a cancellation of long running hub methods from the client.

davidfowl commented 5 years ago

Today we only support cancellation for streams but there's nothing wrong with supporting this for other invocations.

analogrelay commented 5 years ago

I believe we support it for non-streaming as well... As long as you accept a CancellationToken in your Hub method, we'll automatically flow it forward. For example, the below should work:

// Server
public async Task LongRunningOperationAsync(string foo, int bar, CancellationToken cancellationToken)
{
}

// Client
var result = await connection.InvokeAsync("foo", 42, someCancellationToken);

If it doesn't, let us know!

BrennanConroy commented 5 years ago

@anurse I don't think we do support that, but we structured the code in such a way that we could add support for it in the future.

analogrelay commented 5 years ago

Ah yes, I believe the server supports it but the client does not. Moving to the backlog to add this support.

thw0rted commented 4 years ago

I'm not really a SignalR user, just inherited a project that uses it, but can I make a suggestion? If you're going to support cancellation of an in-flight request, instead of rolling your own "token", just take an AbortSignal. This is what the Fetch API already uses, so if you're using Fetch under the hood, it's as simple as passing the signal argument through.

analogrelay commented 4 years ago

instead of rolling your own "token", just take an AbortSignal.

That's for JavaScript though. This issue is discussing the .NET Client, where CancellationToken is the equivalent primitive.

If you're looking for changes to JavaScript cancellation (I don't think we support cancellation of server methods in JS at all right now, so it's certainly a good discussion to have!) then I'd suggest filing a new issue.

ghost commented 4 years ago

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

muzopraha commented 3 years ago

Hi, we need this functionality too. We have been waiting for this feature several months and one of our motivations for upgrading to .NET5 was this feature which was presented in .NET5 backlog. Now I see, it is not implemented yet :/ Is there any ETA for this one? Thank you.

davidfowl commented 3 years ago

@muzopraha What's the scenario you have with a long running hub method?

alexalok commented 3 years ago

Sorry to hijack the issue but I'd also like to report one possible scenario.

Our company uses SignalR as a signal server for WebRTC. There are 3 main actors involved: caller, signaling server and a callee. There's a method the caller calls to initiate a call and notify the callee. Ideally, method would wait for the callee to answer and return the information required for the call to be established. There is a problem, however: if the caller decides to cancel a call there's no way for us to cancel the initialization method. Due to this issue, currently we immediately return from the initialization method and use another server->client callback to inform of the result and one more client->server method to inform the signaling server if the caller has cancelled the call. If we had a way for the caller to cancel the initialization method it would remove the need of those two additional methods.

muzopraha commented 3 years ago

@muzopraha What's the scenario you have with a long running hub method? @davidfowl The scenario is, that our clients can run async heavy-weight server side operations (gathering data from other services) via SignalR. These clients waits for results (listen to SignalR), but it is quite common that these clients need to stop processing these requests. Now, we are cancelling these async operations via cancellation token on client side, but server-side is still running even if it is not necessary. Our solution architecture is like this: Clients (WPF/Blazor/etc) => WebAPI => Application => DB

thorgeirk11 commented 2 years ago

@davidfowl Any updates on this?

davidfowl commented 2 years ago

@thorgeirk11 No updates, best to add your scenario so we have a record of what people want to use it for before we invest the time.

thw0rted commented 2 years ago

If you're collecting use cases, I can briefly describe mine.

I load data to show on a map, from a variety of sources. One provides records via SignalR. If the user drags the map, I can start loading new records for the area that has just scrolled into view. If they drag it again before the network request has finished, I may no longer need those records, so I can cancel the in-flight request. With Fetch / AbortSignal, this process is very simple and works perfectly.

secretwpn commented 2 years ago

@thw0rted your use case sounds like it could use SignalR streaming to stream a list of records matching the new viewport. I have somewhat similar use case and streaming worked well for me.

thw0rted commented 2 years ago

Thanks for the link @secretwpn , that's basically exactly what I had in mind. In fact, the API maps almost exactly to the RxJS Observable pattern which I already use extensively. Of course, I'm only consuming somebody else's back end API so I'm not sure that the stream method is available for the service I use, but I will make a note to come back and look into it when I have time.

MaximMikhisor commented 2 years ago

@thorgeirk11 No updates, best to add your scenario so we have a record of what people want to use it for before we invest the time.

We also need this functionality. User starts some long runing process on server and waiting to get data. He has option to cancel this process on Client side. But, unfortunately, this cancelation means nothing on Server side. As result user can star-stop N times this process. And all these N processes will be running on server side until they will be finished.

From our point of view cancelation is quite important functionality. Especially if take in account that implemented interfaces assume it should work (i.e. presence of CancelationToken). It took some time for us to figure out that it does not work.

Please implement this ticket. Thank you.

EmanH commented 2 years ago

According to the docs this should be possible.

image

However, this doesn't seem to work in my tests. Calling the dispose method on ISubscription stops the stream client-side, but the server continues to stream events.

EmanH commented 2 years ago

Our use-case is streaming a file download from server to client. The client should be able to cancel the download and stop the file chunks from being sent from the server.

BrennanConroy commented 2 years ago

File a new issue and show your code.

EmanH commented 2 years ago

Actually this issue might just relate to how Chrome dev tools does network throttling, which is how we're testing. With network throttling on there seems to be a buffer of events which continues to be sent even after dispose is called. The stream is actually being cancelled on the server. So probably no issue in the real world.

banoz commented 2 years ago
// Server
public async Task LongRunningOperationAsync(string foo, int bar, CancellationToken cancellationToken)
{
}

// Client
var result = await connection.InvokeAsync("foo", 42, someCancellationToken);

It doesn't work for me.

First of all, it looks like client side call in your example missing a first parameter which is a called method name ("LongRunningOperationAsync").

And even with a proper first parameter it still doesn't work. It works only when I remove cancellationToken parameter from the server side method declaration.

The issue looks the same as this.

Is there some attribute that could help?

I'm using 3.1.23, can't go higher than Core 3.1

BrennanConroy commented 2 years ago

CancellationToken in the hub method signature is only supported for Server to client streaming currently.

This issue is tracking adding it for other cases.

muzopraha commented 2 years ago

@davidfowl Any updates on this?

Cizzl commented 1 year ago

any update?

hershelJ commented 12 months ago

Any update on this?

It's an accepted overload, just confusing to as why the hub method would not be able to capture this?

InvokeAsync: public static Task InvokeAsync(this HubConnection hubConnection, string methodName, object? arg1, CancellationToken cancellationToken = default) { return hubConnection.InvokeCoreAsync(methodName, new[] { arg1 }, cancellationToken); }

Description I am experiencing an issue with SignalR's InvokeAsync method in a Blazor WebAssembly application. The problem arises when invoking a server-side Hub method with what seems like the correct number and types of parameters, yet I receive an error indicating a mismatch.

Environment Framework: Blazor WebAssembly SignalR Version: 8.0.0 .NET Version: 8.0.0

Error Message: dbug: Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher[22] Parameters to hub method 'UpdateChannelAsync' are incorrect. System.IO.InvalidDataException: Invocation provides 1 argument(s) but target expects 2.

Client Side: public async Task CreateOrUpdateChannelAsync(ChannelsViewModel channel, CancellationToken cts) { // [Include relevant client-side code, focusing on the invocation part] _signalRConnectionService.HubConnection.InvokeAsync("UpdateChannelAsync", channel, cts); }

Server-Side Hub Method: public async Task UpdateChannelAsync(TPMChannels channel, CancellationToken cts = default) { // [Include relevant server-side hub method code] }

BrennanConroy commented 11 months ago

Since this feature isn't implemented yet that method signature won't work. It's basically trying to receive a serialized CancellationToken from the client, which isn't going to work.

The cancellation token in InvokeAsync will cancel the client waiting for a server response, but it doesn't pass that info along to the server yet. That's what this issue is tracking adding support for.

public async Task UpdateChannelAsync(TPMChannels channel, CancellationToken cts = default) -> public async Task UpdateChannelAsync(TPMChannels channel)

codymullins commented 1 month ago

@davidfowl if this is something the team would be interested in supporting, let us know. If you could give us an idea of the implementation you'd prefer we can band together to make progress here.

davidfowl commented 1 month ago

I'll defer to @BrennanConroy here.

BrennanConroy commented 1 month ago

Basically we would need to update the client(s) to send a CancelInvocationMessage when the cancellation token passed in to InvokeAsync is canceled. We currently only send that message type when the client cancels a stream https://github.com/dotnet/aspnetcore/blob/54c0cc8fa74e8196a2ce0711a20959143be7fb6f/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs#L708

The server is mostly setup to handle CancelInvocationMessage https://github.com/dotnet/aspnetcore/blob/54c0cc8fa74e8196a2ce0711a20959143be7fb6f/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs#L183-L189 The logs would likely need to be updated since it assumes streams are the only thing that can be canceled.

We'd also need to update the logic around which hub methods can have a synthetic argument (CancellationToken) https://github.com/dotnet/aspnetcore/blob/54c0cc8fa74e8196a2ce0711a20959143be7fb6f/src/SignalR/server/Core/src/Internal/HubMethodDescriptor.cs#L64-L66


A massive stretch goal would be to do the same thing for client results, which would require adding synthetic argument support on the client side and sending CancelInvocationMessage from the server. That work is tracked by https://github.com/dotnet/aspnetcore/issues/44831 and shouldn't restrict this issue from being worked on.