SignalR Long Running Process - asp.net

I have setup a SignalR hub which has the following method:
public void SomeFunction(int SomeID)
{
try
{
Thread.Sleep(600000);
Clients.Caller.sendComplete("Complete");
}
catch (Exception ex)
{
// Exception Handling
}
finally
{
// Some Actions
}
m_Logger.Trace("*****Trying To Exit*****");
}
The issue I am having is that SignalR initiates and defaults to Server Sent Events and then hangs. Even though the function/method exits minutes later (10 minutes) the method is initiated again ( > 3 minutes) even when the sendComplete and hub.stop() methods are initiated/called on the client prior. Should the user stay on the page the initial "/send?" request stays open indefinitely. Any assistance is greatly appreciated.

To avoid blocking the method for so long, you could use a Taskand call the client method asynchronously.
public void SomeFunction(Int32 id)
{
var connectionId = this.Context.ConnectionId;
Task.Delay(600000).ContinueWith(t =>
{
var message = String.Format("The operation has completed. The ID was: {0}.", id);
var context = GlobalHost.ConnectionManager.GetHubContext<SomeHub>();
context.Clients.Client(connectionId).SendComplete(message);
});
}
Hubs are created when request arrives and destroyed after response is sent down the wire, so in the continuation task, you need to create a new context for yourself to be able to work with a client by their connection identifier, since the original hub instance will no longer be around to provide you with the Clients method.
Also note that you can leverage the nicer syntax that uses async and await keywords for describing asynchronous program flow. See examples at The ASP.NET Site's SignalR Hubs API Guide.

Related

SignalR return value from client method

Hello I'm developing a Server-Client application that communicate with SignalR. What I have to implement is a mechanism that will allow my server to call method on client and get a result of that call. Both applications are developed with .Net Core.
My concept is, Server invokes a method on Client providing Id of that invocation, the client executes the method and in response calls the method on the Server with method result and provided Id so the Server can match the Invocation with the result.
Usage is looking like this:
var invocationResult = await Clients
.Client(connectionId)
.GetName(id)
.AwaitInvocationResult<string>(ClientInvocationHelper._invocationResults, id);
AwaitInvocationResult - is a extension method to Task
public static Task<TResultType> AwaitInvocationResult<TResultType>(this Task invoke, ConcurrentDictionary<string, object> lookupDirectory, InvocationId id)
{
return Task.Run(() =>
{
while (!ClientInvocationHelper._invocationResults.ContainsKey(id.Value)
|| ClientInvocationHelper._invocationResults[id.Value] == null)
{
Thread.Sleep(500);
}
try
{
object data;
var stingifyData = lookupDirectory[id.Value].ToString();
//First we should check if invocation response contains exception
if (IsClientInvocationException(stingifyData, out ClientInvocationException exception))
{
throw exception;
}
if (typeof(TResultType) == typeof(string))
{
data = lookupDirectory[id.Value].ToString();
}
else
{
data = JsonConvert.DeserializeObject<TResultType>(stingifyData);
}
var result = (TResultType)data;
return Task.FromResult(result);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
});
}
As you can see basically I have a dictionary where key is invocation Id and value is a result of that invocation that the client can report. In a while loop I'm checking if the result is already available for server to consume, if it is, the result is converted to specific type.
This mechanism is working pretty well but I'm observing weird behaviour that I don't understand.
If I call this method with await modifier the method in Hub that is responsible to receive a result from client is never invoked.
///This method gets called by the client to return a value of specific invocation
public Task OnInvocationResult(InvocationId invocationId, object data)
{
ClientInvocationHelper._invocationResults[invocationId.Value] = data;
return Task.CompletedTask;
}
In result the while loop of AwaitInvocationResult never ends and the Hub is blocked.
Maby someone can explain this behaviour to me so I can change my approach or improve my code.
As it was mentioned in the answer by Brennan, before ASP.NET Core 5.0 SignalR connection was only able to handle one not streaming invocation of hub method at time. And since your invocation was blocked, server wasn't able to handle next invocation.
But in this case you probably can try to handle client responses in separate hub like below.
public class InvocationResultHandlerHub : Hub
{
public Task HandleResult(int invocationId, string result)
{
InvoctionHelper.SetResult(invocationId, result);
return Task.CompletedTask;
}
}
While hub method invocation is blocked, no other hub methods can be invoked by caller connection. But since client have separate connection for each hub, he will be able to invoke methods on other hubs. Probably not the best way, because client won't be able to reach first hub until response will be posted.
Other way you can try is streaming invocations. Currently SignalR doesn't await them to handle next message, so server will handle invocations and other messages between streaming calls.
You can check this behavior here in Invoke method, invocation isn't awaited when it is stream
https://github.com/dotnet/aspnetcore/blob/c8994712d8c3c982111e4f1a09061998a81d68aa/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs#L371
So you can try to add some dummy streaming parameter that you will not use:
public async Task TriggerRequestWithResult(string resultToSend, IAsyncEnumerable<int> stream)
{
var invocationId = InvoctionHelper.ResolveInvocationId();
await Clients.Caller.SendAsync("returnProvidedString", invocationId, resultToSend);
var result = await InvoctionHelper.ActiveWaitForInvocationResult<string>(invocationId);
Debug.WriteLine(result);
}
and on the client side you will also need to create and populate this parameter:
var stringResult = document.getElementById("syncCallString").value;
var dummySubject = new signalR.Subject();
resultsConnection.invoke("TriggerRequestWithResult", stringResult, dummySubject);
dummySubject.complete();
More details: https://learn.microsoft.com/en-us/aspnet/core/signalr/streaming?view=aspnetcore-5.0
If you can use ASP.NET Core 5, you can try to use new MaximumParallelInvocationsPerClient hub option. It will allow several invocations to execute in parallel for one connection. But if your client will call too much hub methods without providing result, connection will hang.
More details: https://learn.microsoft.com/en-us/aspnet/core/signalr/configuration?view=aspnetcore-5.0&tabs=dotnet
Actually, since returning values from client invocations isn't implemented by SignalR, maybe you can try to look into streams to return values into hubs?
This is supported in .NET 7 now https://learn.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-7.0#client-results
By default a client can only have one hub method running at a time on the server. This means that when you wait for a result in the first hub method, the second hub method will never run since the first hub method is blocking the processing loop.
It would be better if the OnInvocationResult method ran the logic in your AwaitInvocationResult extension and the first hub method just registers the id and calls the client.

How to handle socket exception when net is not there?

I want to display an error screen if net is not there. I am not using connectivity package because I don't want continuous check. I just want to handle exception while calling backend api and display the screen. I am unable to catch the exception.
I found this issue and this question about socket exceptions but none seem to help me.
This is how I call my backend api -
callBackendApi() async {
try {
http.Response response = await Future.value(/*api call here*/)
.timeout(Duration(seconds: 90), onTimeout: () {
print('TIME OUT HAPPENED');
});
} catch (exception) {
Fluttertoast.showToast(msg: 'Check internet connection.');
print('Error occurred' + exception.toString());
}
}
I use dio like this:
try {
var formData = FormData.from(Map<String, dynamic>.from(data));
var response = await dio.post(
uri,
data: formData,
);
jsonResponse = json.decode(response.data);
} on DioError catch (e) {
if (DioErrorType.RECEIVE_TIMEOUT == e.type ||
DioErrorType.CONNECT_TIMEOUT == e.type) {
throw CommunicationTimeoutException(
"Server is not reachable. Please verify your internet connection and try again");
} else if (DioErrorType.RESPONSE == e.type) {
// 4xx 5xx response
// throw exception...
} else if (DioErrorType.DEFAULT == e.type) {
if (e.message.contains('SocketException')) {
throw CommunicationTimeoutException('blabla');
}
} else {
throw CommunicationException("Problem connecting to the server. Please try again.");
}
}
My solution is to import 'dart.io' in order to catch SocketException from try block:
import 'package:http/http.dart' as http;
import 'dart:io';
try{
//Handle you network call code block in here
}on SocketException catch(_){
//To handle Socket Exception in case network connection is not available during initiating your network call
}
Well i don't know if my answer will be solving your question but days ago i had a problem little bit likely yours but in my case was using firebase realtime database. I was asking to myself how can i protect my app from network fails like no internet connection available? Well i am not using connectivity package too so i solve this problem with an approach that you already has been try using a timeout for network operations. I will share two snipets with differents approaches that i had implemented to handle this kind of problem adding some comments trying explain the differences between them.
Approach 1 - Setting timeout outside from network request method
Well the snipet below is a simple firebase database request where _viewsRef is a DatabaseReference and the once method do the request and returns me a Future with or without data.
// get users visualization from realtime database and returns a future
static Future<DataSnapshot> getUserVisualizations({#required String uid}) async {
return _viewsRef.child(uid).limitToLast(50).once();
}
In my BLoC component class i am calling the method below and setting a timeout to the future that is returned.
myBlocComponentMethod(){
//.. some work and finally the call
FirebaseUserViewsHelper.getUserVisualizations(uid: _currentUid)
.then(
(dataSnapshot){
if (dataSnapshot.value == null) {
// do some things to handle no data
}
else {
/// handle your data here
});
}
} // setting timeout here is an important point
).timeout( Duration(seconds: Constants.NETWORK_TIMEOUT_SECONDS),
onTimeout: (){
// method to handle a timeout exception and tell to view layer that
// network operation fails
// if we do not implement onTimeout callback the framework will throw a TimeoutException
} );
}
Well what is the point here? In this case if the timeout expires and future is not completed yet onTimeout callback is called and there i can tell to the view layer that network operation fails and show to the user some widget about it. But even with timeout expired the request to firebase database stays happening again and again, it's like the async event of request the database stays on dart event queue. I think this behavior is bad for performance aspects but if you're building your UI using a StreamBuilder with a little logic and code your requested data will be available right when you internet connection is back and with BLoC pattern the UI can respond easily to this event and we don't need provide a refresh button by example to user make the request again. I don't know if this is the right approach to implement this behavior but it works.
Approach 2 - Setting timeout inside from network request method
Below another firebase database request method
static Future<DataSnapshot> readUserNode( {#required String uid} ) async
=> USERS_REFERENCE.child(uid).once()
.timeout( Duration(seconds: Constants.NETWORK_TIMEOUT_SECONDS ) );
//note: Without timeout callback this line will throw a TimeoutException if the time expires
The usage in another BLoc component:
myBlocComponentMethod2(){
for( String uid in iterable ){
FirebaseUserHelper.readUserNode(uid: uid)
.then( (userSnapshot){
if (userSnapshot.value == null){
// do your stuffs
}
else {
// more stuffs to do
}
}).catchError( (error){
// when timeout expired we will catch the TimeoutException HERE and handling telling
// the UI what we need
} );
}
}
The big difference here that i get was in the behavior. In this second case since i put the timeout inside the request method when the timeout expires the request event do not run anymore, it's like that request event is removed from dart event queue. This can be good from performance perspective but now we need provide a refresh button in UI for user do the data again to get data from internet again.
I don't know if this workaround will solve your problem because you tell about SocketException what is not the case that i has described and i don't know what api you are using to make your requests. Anyway i hope that the concepts described in this post helps you implement a solution in your problem.

Changing my code to use async methods instead of sync methods will force my WebClient to never timeout (20 minute++)

I have 2 asp.net MVC web applications , as follow:-
ApplicationA . which is an Asp.net mvc-4 deployed under iis-8.
ApplicationB. which is an Asp.net mvc-5 deployed under iis-8.
now inside my ApplicationA i have the following method,which will call an action method (home/sync) on applicationB , as follow:-
public List<Technology> GetTechnology(int? currentfiltertype)
{
try
{
using (WebClient wc = new WebClient())
{
string url = currentURL + "home/sync?filtertype=" + currentfiltertype;
wc.Headers.Add("Authorization", token);
string json = wc.DownloadString(url);
List<Technology> result = JsonConvert.DeserializeObject<List<Technology>>(json);
return result;
}
}
catch (Exception e){}
}
now i have noted that when the WebClient calls the action method, and the method did not receive a response within around 2 minutes it will raise a timeout exception. But since the home/sync action method on web application B needs around 30 minutes to complete.. so i was searching for a solution to extend the web-client timeout period. so i tried changing my code to use async methods as follow,mainly by replacing wc.DownloadString with wc.DownloadStringTaskAsync as follow:-
public async Task<List<Technology>> GetTechnology(int? currentfiltertype)
{
try
{
using (WebClient wc = new WebClient())
{
string url = currentURL + "home/sync?filtertype=" + currentfiltertype;
wc.Headers.Add("Authorization", token);
string json = await wc.DownloadStringTaskAsync(url);
List<Technology> result = JsonConvert.DeserializeObject<List<Technology>>(json);
return result;
}
}
catch (Exception e) {}
}
and now seems the WebClient will never expired ... i tried calling the action method and the web client keep waiting for a response for more than 20 minutes without raising any timeout exception, then it received the response from web applicationB and everything worked well..
so can anyone advice why changing my code to use async methods as shown in the above code, caused the WebClient to not timeout ?? i can not understand the relation between using async logic and extending the timeout period for the web-client (not sure if the WebClient will ever timeout inside async methods!!)?
can anyone advice why changing my code to use async methods as shown in the above code, caused the WebClient to not timeout ??
The answer is a bit convoluted: WebClient is based on WebRequest, and HttpWebRequest's Timeout property is only honored for synchronous requests.
(noy sure if the WebClient will ever timeout inside async methods!!)?
It does not directly support asynchronous timeouts, but it does support (its own kind of) cancellation, which you can trigger after a timer.

Scatter / Gather using Rebus

I have a requirement to a batch a number of web service calls on the receipt of a single message appearing in a (MSMQ) queue.
Is "sagas" the way to go?
The interaction with the 3rd party web service is further complicated because I need to call it once, then poll subsequently for an acknowledgement with a correlation id, returned in the reply to the initial call to the web service.
Yes, sagas could be the way to coordinate the process of initiating the call, polling until the operation has ended, and then doing something else when all the work is done.
If you don't care too much about accidentally making the web service call more than once, you can easily use Rebus' async capabilities to implement the polling - I am currently in the process of building something that basically does this:
public async Task Handle(SomeMessage message)
{
var response = await _client.Get<SomeResponse>("https://someurl") ;
var pollUrl = response.PollUrl;
var resultUrl = response.ResultUrl;
while(true)
{
var result = await _client.Get<PollResult>(pollUrl);
if (result.Status == PollStatus.Processing)
{
await Task.Delay(TimeSpan.FromSeconds(2));
continue;
}
if (result.Status == PollStatus.Done)
{
var finalResult = await _client.Get<FinalResult>(resultUrl);
return new SomeReply(finalResult);;
}
throw new Exception($"Unexpected status while polling {pollUrl}: {result.Status}")
}
}
thus taking advantage of async/await to poll the external webservice while it is processing, while consuming minimal resources in our end.

SignalR recording when a Web Page has closed

I am using MassTransit request and response with SignalR. The web site makes a request to a windows service that creates a file. When the file has been created the windows service will send a response message back to the web site. The web site will open the file and make it available for the users to see. I want to handle the scenario where the user closes the web page before the file is created. In that case I want the created file to be emailed to them.
Regardless of whether the user has closed the web page or not, the message handler for the response message will be run. What I want to be able to do is have some way of knowing within the response message handler that the web page has been closed. This is what I have done already. It doesnt work but it does illustrate my thinking. On the web page I have
$(window).unload(function () {
if (event.clientY < 0) {
// $.connection.hub.stop();
$.connection.exportcreate.setIsDisconnected();
}
});
exportcreate is my Hub name. In setIsDisconnected would I set a property on Caller? Lets say I successfully set a property to indicate that the web page has been closed. How do I find out that value in the response message handler. This is what it does now
protected void BasicResponseHandler(BasicResponse message)
{
string groupName = CorrelationIdGroupName(message.CorrelationId);
GetClients()[groupName].display(message.ExportGuid);
}
private static dynamic GetClients()
{
return AspNetHost.DependencyResolver.Resolve<IConnectionManager>().GetClients<ExportCreateHub>();
}
I am using the message correlation id as a group. Now for me the ExportGuid on the message is very important. That is used to identify the file. So if I am going to email the created file I have to do it within the response handler because I need the ExportGuid value. If I did store a value on Caller in my hub for the web page close, how would I access it in the response handler.
Just in case you need to know. display is defined on the web page as
exportCreate.display = function (guid) {
setTimeout(function () {
top.location.href = 'GetExport.ashx?guid=' + guid;
}, 500);
};
GetExport.ashx opens the file and returns it as a response.
Thank you,
Regards Ben
I think a better bet would be to implement proper connection handling. Specifically, have your hub implementing IDisconnect and IConnected. You would then have a mapping of connectionId to document Guid.
public Task Connect()
{
connectionManager.MapConnectionToUser(Context.ConnectionId, Context.User.Name);
}
public Task Disconnect()
{
var connectionId = Context.ConnectionId;
var docId = connectionManager.LookupDocumentId(connectionId);
if (docId != Guid.Empty)
{
var userName = connectionManager.GetUserFromConnectionId(connectionId);
var user = userRepository.GetUserByUserName(userName);
bus.Publish( new EmailDocumentToUserCommand(docId, user.Email));
}
}
// Call from client
public void GenerateDocument(ClientParameters docParameters)
{
var docId = Guid.NewGuid();
connectionManager.MapDocumentIdToConnection(Context.ConnectionId, docId);
var command = new CreateDocumentCommand(docParameters);
command.Correlationid = docId;
bus.Publish(command);
Caller.creatingDocument(docId);
}
// Acknowledge you got the doc.
// Call this from the display method on the client.
// If this is not called, the disconnect method will handle sending
// by email.
public void Ack(Guid docId)
{
connectionManager.UnmapDocumentFromConnectionId(connectionId, docId);
Caller.sendMessage("ok");
}
Of course this is from the top of my head.

Resources