I'm encountering some problems with a signalR hub , i need to invoke a certain method
from a signalR hub whenever i receive a request from an external service .
Whenever i do receive such "request" i connect to the signalRhub and then invoke one of its methods.
string hostname = Environment.GetEnvironmentVariable("FRONTEND_HOSTNAME");
HubConnection connection;
connection = new HubConnectionBuilder()
.WithUrl($"{hostname}/xxx")
.Build();
await connection.StartAsync();
var resp = new
{
xx:'xx',
};
await connection.InvokeAsync("SendReport", resp);
connection.Closed += async (error) =>
{
await Task.Delay(new Random().Next(0, 5) * 1000);
await connection.StartAsync();
};
The whole implementation has already been done , although it seems that i'm able to connect & send messages ALMOST always without problems,
however from time to time the StartAsync() method fails and throws an exception "Name or service not known" , to me it looks like it's unable to connect due to network issues , if you had to implement a way to retry the StartAsync a few times to then invoke the hub method, how would you do it?
I'm not asking for a straight solution , an example of something / links to documentation would still be great, i did search for a way to handle this but with no luck
Sorry it appears i didn't look properly within the documentation ,
here's the answer i was looking for:
public static async Task<bool> ConnectWithRetryAsync(HubConnection connection, CancellationToken token){
// Keep trying to until we can start or the token is canceled.
while (true)
{
try
{
await connection.StartAsync(token);
Debug.Assert(connection.State == HubConnectionState.Connected);
return true;
}
catch when (token.IsCancellationRequested)
{
return false;
}
catch
{
// Failed to connect, trying again in 5000 ms.
Debug.Assert(connection.State == HubConnectionState.Disconnected);
await Task.Delay(5000);
}
}}
https://learn.microsoft.com/en-us/aspnet/core/signalr/dotnet-client?view=aspnetcore-6.0&tabs=visual-studio
Related
I'm communicating with a custom external device, and this device requires when I send data, I must send the same data once again, between 10 and 100 milliseconds (send the data twice 10-100 ms interval).
Basically it's working very well to send the data, but the second sending is between 500 - 800 milliseconds, so I must decrease it.
I using Plugin.BLE
...
public async Task WriteCharacteristics(byte value)
{
try
{
byte[] data = {value};
await Service.GetCharacteristicsAsync().ContinueWith(async task =>
{
Characteristics = task.Result;
await Characteristics.First().WriteAsync(data).ContinueWith(async t1 =>
{
await Characteristics.First().StartUpdatesAsync();
});
});
}
catch (Exception e)
{
Debug.WriteLine("********* Failed to get ReadCharacteristics: " + e.Message);
}
}
...
and when I call:
...
_ = ble.WriteCharacteristics((byte)value).ContinueWith(_ =>
{
Thread.Sleep(10);
ble.WriteCharacteristics((byte)value).Wait();
});
...
I tried to call the function twice, as parallel, but that solution not good.
As you see when I call, it waits 10 milliseconds, and want to send the data again. Is there any workaround to solve this problem?
One reason for the long delay between write operations could be the writeType. There are two types, writeWithResponse and writeWithoutResponse. The first is the safer method because you will receive a confirmation if your message was sent correctly. This obviously restricts data throughput.
Setting the writeType to writeWithoutResponse could work in your case. This issue on github shows the correct way:
Characteristics.WriteType = Plugin.BLE.Abstractions.CharacteristicWriteType.WithoutResponse;
I see two issues that can be improved:
As mentioned by JonasH in a comment, don't use async/await and ContinueWith together. await is sufficient to suspend your code until an async Task completes.
Re-use the "setup" of the communications, so that only the final call needs to be done twice.
I'm not familiar with that plugin (looked at its docs just now, to confirm the basic approach), so this code may need some adjustment, but hopefully this will be a starting point:
public async Task WriteByteTwice(byte value)
{
try
{
// Plug-in docs say WriteAsync call should be on MAIN THREAD.
if (!Xamarin.Essentials.MainThread.IsMainThread)
throw new InvalidProgramException("WriteByteTwice must be called from MainThread");
byte[] data = {value};
Characteristics = await Service.GetCharacteristicsAsync();
// Send data first time.
await Characteristics.First().WriteAsync(data);
await Characteristics.First().StartUpdatesAsync();
await Task.Delay(10);
// Send data again.
await Characteristics.First().WriteAsync(data);
await Characteristics.First().StartUpdatesAsync();
}
catch (Exception e)
{
Debug.WriteLine("********* Failed to get ReadCharacteristics: " + e.Message);
}
}
It works fine when have one or two tasks however throws an error "A task was cancelled" when we have more than one task listed.
List<Task> allTasks = new List<Task>();
allTasks.Add(....);
allTasks.Add(....);
Task.WaitAll(allTasks.ToArray(), configuration.CancellationToken);
private static Task<T> HttpClientSendAsync<T>(string url, object data, HttpMethod method, string contentType, CancellationToken token)
{
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(method, url);
HttpClient httpClient = new HttpClient();
httpClient.Timeout = new TimeSpan(Constants.TimeOut);
if (data != null)
{
byte[] byteArray = Encoding.ASCII.GetBytes(Helper.ToJSON(data));
MemoryStream memoryStream = new MemoryStream(byteArray);
httpRequestMessage.Content = new StringContent(new StreamReader(memoryStream).ReadToEnd(), Encoding.UTF8, contentType);
}
return httpClient.SendAsync(httpRequestMessage).ContinueWith(task =>
{
var response = task.Result;
return response.Content.ReadAsStringAsync().ContinueWith(stringTask =>
{
var json = stringTask.Result;
return Helper.FromJSON<T>(json);
});
}).Unwrap();
}
There's 2 likely reasons that a TaskCanceledException would be thrown:
Something called Cancel() on the CancellationTokenSource associated with the cancellation token before the task completed.
The request timed out, i.e. didn't complete within the timespan you specified on HttpClient.Timeout.
My guess is it was a timeout. (If it was an explicit cancellation, you probably would have figured that out.) You can be more certain by inspecting the exception:
try
{
var response = task.Result;
}
catch (TaskCanceledException ex)
{
// Check ex.CancellationToken.IsCancellationRequested here.
// If false, it's pretty safe to assume it was a timeout.
}
I ran into this issue because my Main() method wasn't waiting for the task to complete before returning, so the Task<HttpResponseMessage> was being cancelled when my console program exited.
C# ≥ 7.1
You can make the main method asynchronous and await the task.
public static async Task Main(){
Task<HttpResponseMessage> myTask = sendRequest(); // however you create the Task
HttpResponseMessage response = await myTask;
// process the response
}
C# < 7.1
The solution was to call myTask.GetAwaiter().GetResult() in Main() (from this answer).
var clientHttp = new HttpClient();
clientHttp.Timeout = TimeSpan.FromMinutes(30);
The above is the best approach for waiting on a large request.
You are confused about 30 minutes; it's random time and you can give any time that you want.
In other words, request will not wait for 30 minutes if they get results before 30 minutes.
30 min means request processing time is 30 min.
When we occurred error "Task was cancelled", or large data request requirements.
Another possibility is that the result is not awaited on the client side. This can happen if any one method on the call stack does not use the await keyword to wait for the call to be completed.
Promoting #JobaDiniz's comment to an answer:
Do not do the obvious thing and dispose the HttpClient instance, even though the code "looks right":
async Task<HttpResponseMessage> Method() {
using (var client = new HttpClient())
return client.GetAsync(request);
}
Disposing the HttpClient instance can cause following HTTP requests started by other instances of HttpClient to be cancelled!
The same happens with C#'s new RIAA syntax; slightly less obvious:
async Task<HttpResponseMessage> Method() {
using var client = new HttpClient();
return client.GetAsync(request);
}
Instead, the correct approach is to cache a static instance of HttpClient for your app or library, and reuse it:
static HttpClient client = new HttpClient();
async Task<HttpResponseMessage> Method() {
return client.GetAsync(request);
}
(The Async() request methods are all thread safe.)
in my .net core 3.1 applications I am getting two problem where inner cause was timeout exception.
1, one is i am getting aggregate exception and in it's inner exception was timeout exception
2, other case was Task canceled exception
My solution is
catch (Exception ex)
{
if (ex.InnerException is TimeoutException)
{
ex = ex.InnerException;
}
else if (ex is TaskCanceledException)
{
if ((ex as TaskCanceledException).CancellationToken == null || (ex as TaskCanceledException).CancellationToken.IsCancellationRequested == false)
{
ex = new TimeoutException("Timeout occurred");
}
}
Logger.Fatal(string.Format("Exception at calling {0} :{1}", url, ex.Message), ex);
}
In my situation, the controller method was not made as async and the method called inside the controller method was async.
So I guess its important to use async/await all the way to top level to avoid issues like these.
I was using a simple call instead of async. As soon I added await and made method async it started working fine.
public async Task<T> ExecuteScalarAsync<T>(string query, object parameter = null, CommandType commandType = CommandType.Text) where T : IConvertible
{
using (IDbConnection db = new SqlConnection(_con))
{
return await db.ExecuteScalarAsync<T>(query, parameter, null, null, commandType);
}
}
Another reason can be that if you are running the service (API) and put a breakpoint in the service (and your code is stuck at some breakpoint (e.g Visual Studio solution is showing Debugging instead of Running)). and then hitting the API from the client code. So if the service code a paused on some breakpoint, you just hit F5 in VS.
I need to integrate an API into my development with a specific scenario called "Time Out Reversal" (TOR)
This means, succintly, to comply with the following requirements:
Initiate a request by invoking an endpoint
If a response is not received within a defined time out
start a reversal request by invoking another endpoint
While the requirements seems very clear to me, I really haven't found a way to implement it by using tasks.
For example, I know how to delay a task but I dont really know how to set a timeout in seconds for a started task
Any suggestion?
You can easily implement a timeout like so:
public async Task TimoutReversal() {
var timeout = TimeSpan.FromSeconds(10);
try {
//assumes HttpClient
Client.Timeout = timeout;
await Client.GetAsync(firstEndpoint);
} catch (OperationCanceledException ex) {
await Client.DeleteAsync(secondEndpoint);
}
}
//or
public async Task TimoutReversal() {
var timeout = TimeSpan.FromSeconds(10);
var firstTask = Client.GetAsync(firstEndpoint);
if (await Task.WhenAny(firstTask, Task.Delay(timeout)) == firstTask) {
//success
}else {
//failure
await Client.DeleteAsync(secondEndpoint);
}
}
Also see protected answer here on SO: Task with timeout
Another option is to pass the Token from a CancellationTokenSource created from a TimeSpan to the HttpClient calls.
We have a (long-running) Windows service that among other things periodically communicates with an FTP server embedded on a third-party device using FtpWebRequest. This works great most of the time, but sometimes our service stops communicating with the device, but as soon as you restart our service everything starts working again.
I've spent some time debugging this with an MCVE (included below) and discovered via Wireshark that once communication starts failing there is no network traffic going to the external FTP server (no packets at all show up going to this IP in Wireshark). If I try to connect to the same FTP from another application on the same machine like Windows explorer everything works fine.
Looking at the packets just before everything stops working I see packets with the reset (RST) flag set coming from the device, so I suspect this may be the issue. Once some part of the network stack on the computer our service in running on receives the reset packet it does what's described in the TCP resets section of this article and blocks all further communication from our process to the device.
As far as I can tell there's nothing wrong with the way we're communicating with the device, and most of the time the exact same code works just fine. The easiest way to reproduce the issue (see MCVE below) seems to be to make a lot of separate connections to the FTP at the same time, so I suspect the issue may occur when there are a lot of connections being made to the FTP (not all by us) at the same time.
The thing is that if we do restart our process everything works fine, and we do need to re-establish communication with the device. Is there a way to re-establish communication (after a suitable amount of time has passed) without having to restart the entire process?
Unfortunately the FTP server is running embedded on a fairly old third-party device that's not likely to be updated to address this issue, and even if it were we'd still want/need to communicate with all the ones already out in the field without requiring our customers to update them if possible.
Options we are aware of:
Using a command line FTP client such as the one built into Windows.
One downside to this is that we need to list all the files in a directory and then download only some of them, so we'd have to write logic to parse the response to this.
We'd also have to download the files to a temp file instead of to a stream like we do now.
Creating another application that handles the FTP communication part that we tear down after each request completes.
The main downside here is that inter-process communication is a bit of a pain.
MCVE
This runs in LINQPad and reproduces the issue fairly reliably. Typically the first several tasks succeed and then the issue occurs, and after that all tasks start timing out. In Wireshark I can see that no communication between my computer and the device is happening.
If I run the script again then all tasks fail until I restart LINQPad or do "Cancel All Threads and Reset" which restarts the process LINQPad uses to run the query. If I do either of those things then we're back to the first several tasks succeeding.
async Task Main() {
var tasks = new List<Task>();
var numberOfBatches = 3;
var numberOfTasksPerBatch = 10;
foreach (var batchNumber in Enumerable.Range(1, numberOfBatches)) {
$"Starting tasks in batch {batchNumber}".Dump();
tasks.AddRange(Enumerable.Range(1, numberOfTasksPerBatch).Select(taskNumber => Connect(batchNumber, taskNumber)));
await Task.Delay(TimeSpan.FromSeconds(5));
}
await Task.WhenAll(tasks);
}
async Task Connect(int batchNumber, int taskNumber) {
try {
var client = new FtpClient();
var result = await client.GetFileAsync(new Uri("ftp://192.168.0.191/logging/20140620.csv"), TimeSpan.FromSeconds(10));
result.Count.Dump($"Task {taskNumber} in batch {batchNumber} succeeded");
} catch (Exception e) {
e.Dump($"Task {taskNumber} in batch {batchNumber} failed");
}
}
public class FtpClient {
public virtual async Task<ImmutableList<Byte>> GetFileAsync(Uri fileUri, TimeSpan timeout) {
if (fileUri == null) {
throw new ArgumentNullException(nameof(fileUri));
}
FtpWebRequest ftpWebRequest = (FtpWebRequest)WebRequest.Create(fileUri);
ftpWebRequest.Method = WebRequestMethods.Ftp.DownloadFile;
ftpWebRequest.UseBinary = true;
ftpWebRequest.KeepAlive = false;
using (var source = new CancellationTokenSource(timeout)) {
try {
using (var response = (FtpWebResponse)await ftpWebRequest.GetResponseAsync()
.WithWaitCancellation(source.Token)) {
using (Stream ftpStream = response.GetResponseStream()) {
if (ftpStream == null) {
throw new InvalidOperationException("No response stream");
}
using (var dataStream = new MemoryStream()) {
await ftpStream.CopyToAsync(dataStream, 4096, source.Token)
.WithWaitCancellation(source.Token);
return dataStream.ToArray().ToImmutableList();
}
}
}
} catch (OperationCanceledException) {
throw new WebException(
String.Format("Operation timed out after {0} seconds.", timeout.TotalSeconds),
WebExceptionStatus.Timeout);
} finally {
ftpWebRequest.Abort();
}
}
}
}
public static class TaskCancellationExtensions {
/// http://stackoverflow.com/a/14524565/1512
public static async Task<T> WithWaitCancellation<T>(
this Task<T> task,
CancellationToken cancellationToken) {
// The task completion source.
var tcs = new TaskCompletionSource<Boolean>();
// Register with the cancellation token.
using (cancellationToken.Register(
s => ((TaskCompletionSource<Boolean>)s).TrySetResult(true),
tcs)) {
// If the task waited on is the cancellation token...
if (task != await Task.WhenAny(task, tcs.Task)) {
throw new OperationCanceledException(cancellationToken);
}
}
// Wait for one or the other to complete.
return await task;
}
/// http://stackoverflow.com/a/14524565/1512
public static async Task WithWaitCancellation(
this Task task,
CancellationToken cancellationToken) {
// The task completion source.
var tcs = new TaskCompletionSource<Boolean>();
// Register with the cancellation token.
using (cancellationToken.Register(
s => ((TaskCompletionSource<Boolean>)s).TrySetResult(true),
tcs)) {
// If the task waited on is the cancellation token...
if (task != await Task.WhenAny(task, tcs.Task)) {
throw new OperationCanceledException(cancellationToken);
}
}
// Wait for one or the other to complete.
await task;
}
}
This reminds me of old(?) IE behaviour of no reload of pages even when the network came back after N unsuccessful tries.
You should try setting the FtpWebRequest's cache policy to BypassCache.
HttpRequestCachePolicy bypassPolicy = new HttpRequestCachePolicy(
HttpRequestCacheLevel.BypassCache
);
ftpWebRequest.CachePolicy = bypassPolicy;
after setting KeepAlive.
I had the same issue, when trying to connect to an ftps server without the EnableSsl = true. The connection would fail twice, Wireshark shows the RST command, and then no more requests would leave the network resulting in the timeout exception, even after setting the EnableSsl = true.
I found setting the ConnectionGroupName allows the connection to reset and use a new port.
eg:
request.ConnectionGroupName = Guid.NewGuid();
Beware of port exhaustion using this method however, see https://learn.microsoft.com/en-us/troubleshoot/dotnet/framework/ports-run-out-use-connectiongroupname
I am trying to use Polly to handle exceptions thrown by my WebRequest.
This is my implementation.
var generalExceptionPolicy=Policy.Handle<Exception>().WaitAndRetry(2, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),(exception,timespan)=>{
if(attempt++ == 2)
{
Toast.MakeText(Activity,"No Connection Bro",ToastLength.Short).Show();
}
});
var test = await generalExceptionPolicy.ExecuteAsync(()=>PostPreLogin (view.FindViewById<EditText> (Resource.Id.mobileTextBox).Text));
I have got the retries working. But what I am wondering is where will I get a callback after the last attempt ? I am getting a callback in Policy definition part, where I am trying to display a Toastmessage. But that is only between the trials. I am not getting it after my last trial.
Also, my UI freezes after the last trial. Maybe becuase ExecuteAsync Task did not complete, due to the Exception. If that is so, what is the right approach to use Polly library ?
This is the method that I am trying to handle with Polly
public async Task<string> PostPreLogin(string userName)
{
var preloginvalue = await Account.PreLoginPost (userName);
return preloginvalue;
}
It is easy, basically you need to define the policy and passing the call back later is just as simple . Check this article which describes how to accomplish exactly what you want in details.
Basically, you can define your policy as follows :
async Task<HttpResponseMessage> QueryCurrencyServiceWithRetryPolicy(Func<Task<HttpResponseMessage>> action)
{
int numberOfTimesToRetry = 7;
int retryMultiple = 2;
//Handle HttpRequestException when it occures
var response = await Policy.Handle<HttpRequestException>(ex =>
{
Debug.WriteLine("Request failed due to connectivity issues.");
return true;
})
//wait for a given number of seconds which increases after each retry
.WaitAndRetryAsync(numberOfTimesToRetry, retryCount => TimeSpan.FromSeconds(retryCount * retryMultiple))
//After the retry, Execute the appropriate set of instructions
.ExecuteAsync(async () => await action());
//Return the response message gotten from the http client call.
return response;
}
And you can pass the callback as follows:
var response = await QueryCurrencyServiceWithRetryPolicy(() => _httpClient.GetAsync(ALL_CURRENCIES_URL));
I think what you are after is ExecuteAndCapture instead of Execute:
generalExceptionPolicy.ExecuteAndCapture(() => DoSomething());
Check this out for more details.