What I'm trying to achieve is Given a predicate function and an action which returns the object to check, check predicate, retry and fail after numTries .. or success .
Purpose of this function is :
During constant data collection from network resources ( web, apis, .. ) return of network operations not successful every time ( because of bad servers, network problems , etc.. ). If you need that data you should retry, and if you do this kind of ops. a lot, you should have a function to handle this.
I'm looking for are there better ways to do this.
public static async Task<bool> CheckAndRetryAsync<T>
(
T _object, //object to check state
Func<T, bool> predicate, Func<Task<T>> action, // object creation func
int sleep = 150, // sleep ms. between tries
int numTries = 6, // ..
bool rethrowExceptions = false, // should rethrow exc. during object creation
ManualResetEvent mr = null
)
{
int tries = 0;
bool success = false;
while (true)
{
try
{
_object = await action();
success = predicate(_object);
if (!success && tries < numTries)
{
tries++;
await Task.Delay(sleep);
}
else
{
//goto exit;
break;
}
}
catch (Exception e)
{
if (rethrowExceptions)
throw;
if (tries < numTries){
tries++;
await Task.Delay(sleep);
}
else{
goto exit;
}
}
}
exit:
if(mr != null)
mr.Set();
return success;
}
Tests for function :
public class StaticUtilFuncTests
{
ManualResetEvent mr = new ManualResetEvent(false);
[Fact]
public async Task T_010_CheckAndRetryAsync()
{
int tries = 1;
mr.Reset();
Stopwatch sw = new Stopwatch();
sw.Start();
TObj ttx = null;
bool succsess = await CheckAndRetryAsync<TObj>(ttx,
(o) => (o != null && o.Value != ""),
async () =>
{
if (tries <= 4)
{
ttx = new TObj();
tries++;
}
else
{
ttx = new TObj("sdf");
}
mr.Set();
return ttx;
},
numTries: 5, sleep: 400
);
mr.WaitOne();
long elapsed = sw.ElapsedMilliseconds;
Assert.True(elapsed >= 1600l); // numtries * sleep
Assert.True(succsess);
Assert.True(tries == 5);
}
[Fact]
public async Task T_011_CheckAndRetryAsync()
{
mr.Reset();
Stopwatch sw = new Stopwatch();
sw.Start();
TObj ttx = null;
bool succsess = await CheckAndRetryAsync<TObj>(
ttx,
(o) => (o != null && o.Value != ""),
async () =>
{
ttx = new TObj("");
mr.Set();
return ttx;
},
numTries: 3, sleep: 1100
);
mr.WaitOne();
long elapsed = sw.ElapsedMilliseconds;
Assert.True(elapsed >= 3300l); // numtries * sleep
Assert.False(succsess);
}
[Fact]
public async Task T_012_CheckAndRetryAsync()
{
mr.Reset();
//Mock<TObj> to = new Mock<TObj>();
//to.Setup(
// m => m.)
Stopwatch sw = new Stopwatch();
sw.Start();
TObj ttx = null;
bool succsess = await CheckAndRetryAsync<TObj>(
ttx,
(o) => (o != null && o.Value != ""),
async () =>
{
ttx = new TObj("",true);
//mr.Set();
return ttx;
},
numTries: 30, sleep: 50, mr: mr
);
mr.WaitOne();
long elapsed = sw.ElapsedMilliseconds;
Assert.True(elapsed >= 1500l); // numtries * sleep
Assert.False(succsess);
}
}
public class TObj
{
public string Value;
public TObj(string value = "" , bool testing = false)
{
if(value == "" && testing)
throw new Exception();
Value = value;
}
}
}
A real usage example :
BittrexBtcTicker btcTicker = null;
success |= await CheckAndRetryAsync<BittrexBtcTicker>(
btcTicker, // object we need to get ( also save db )
(o) => (o != null && o.Value > 0), // predicate not null, and bigger than 0
async () =>
{
mr.Reset(); // a noise but needed during async ops.
btcTicker = await BittrexClient
.GetTicker(saveDb: true, updateDb: updateDb, _context: context, mr: mr); // call to object creation..
mr.WaitOne(); // wait to op. complete
return btcTicker;
}
);