I have a generic method for executing an asynchronous tasks in synchronous context with retries.
public static T RunWithRetries<T>(Task<T> task)
{
while(...) // 3 attempts
{
try
{
task.GetAwaiter().GetResult();
}
catch
{
// sleep & retry later in case of some exceptions, for example 429
}
}
}
Then I'm passing any method from asynchronous API to run it like this.
SyncHelper.RunWithRetries(externalAPI.UploadAsync(fileRequest, fileStream));
The problem is that it works unless an exception happened during the request and we need to re-try. If an error happens, all subsequent retries are also throwing the same exception. So, my questions are
- Is this happening because of the fileStream object? It's in the using statement, so it's not being disposed for sure. Can the stream position after first upload attempt be a problem?
- Is it normal that the same Task object is being retried? Should I change the way I'm doing it to something better?
CodePudding user response:
Is this happening because of the
fileStream
object? It's in the using statement, so it's not being disposed for sure. Can the stream position after first upload attempt be a problem?
Yes, this is one of your problems. Whenever a stream is read its Position
is not reset to 0 automatically. If you try to re-read it then it will not read anything since the position is at the end of the stream.
So, you either have to create a new stream each and every time or rewind the stream to the beginning.
Is it normal that the same Task object is being retried? Should I change the way I'm doing it to something better?
Whenever a Task has been finished (either with a particular result or with an exception) then re-await
-ing or retrieving its Result
will not trigger a re-execution. It will simple return the value or the exception.
So, you have to create a new Task for each and every retry attempt. In order to do so you could anticipate a Func<Task<T>>
in your RunWithRetries
public static T RunWithRetries<T>(Func<Task<T>> issueRequest)
{
...
issueRequest().GetAwaiter().GetResult();
}
From the caller side it would look like this:
RunWithRetries(() => externalAPI.UploadAsync(fileRequest, new FileStream(...)));
//or
RunWithRetries(() => { fileStream.Position = 0; externalAPI.UploadAsync(fileRequest, fileStream); });