Ответ 1
ОБНОВЛЕНИЕ 5/2017
Фильтры исключений С# 6 делают предложение catch
намного проще:
private static async Task<T> Retry<T>(Func<T> func, int retryCount)
{
while (true)
{
try
{
var result = await Task.Run(func);
return result;
}
catch when (retryCount-- > 0){}
}
}
и рекурсивная версия:
private static async Task<T> Retry<T>(Func<T> func, int retryCount)
{
try
{
var result = await Task.Run(func);
return result;
}
catch when (retryCount-- > 0){}
return await Retry(func, retryCount);
}
ОРИГИНАЛ
Существует множество способов кодирования функции Retry: вы можете использовать рекурсию или итерацию задачи. В греческой группе .NET User было обсуждение, а затем повторить разные способы сделать именно это.
Если вы используете F #, вы также можете использовать конструкции Async. К сожалению, вы не можете использовать конструкции async/wait, по крайней мере, в Async CTP, потому что код, сгенерированный компилятором, не любит множественных ожиданий или возможных повторов в блоках catch.
Рекурсивная версия - это, пожалуй, самый простой способ создания Retry в С#. Следующая версия не использует Unwrap и добавляет дополнительную задержку перед повторением:
private static Task<T> Retry<T>(Func<T> func, int retryCount, int delay, TaskCompletionSource<T> tcs = null)
{
if (tcs == null)
tcs = new TaskCompletionSource<T>();
Task.Factory.StartNew(func).ContinueWith(_original =>
{
if (_original.IsFaulted)
{
if (retryCount == 0)
tcs.SetException(_original.Exception.InnerExceptions);
else
Task.Factory.StartNewDelayed(delay).ContinueWith(t =>
{
Retry(func, retryCount - 1, delay,tcs);
});
}
else
tcs.SetResult(_original.Result);
});
return tcs.Task;
}
Функция StartNewDelayed исходит из ParallelExtensionsExtras образцов и использует таймер для запуска источника TaskCompletion, когда происходит таймаут.
Версия F # намного проще:
let retry (asyncComputation : Async<'T>) (retryCount : int) : Async<'T> =
let rec retry' retryCount =
async {
try
let! result = asyncComputation
return result
with exn ->
if retryCount = 0 then
return raise exn
else
return! retry' (retryCount - 1)
}
retry' retryCount
Unfortunatley, невозможно написать что-то подобное в С#, используя async/await из Async CTP, потому что компилятор не любит ожидания операторов внутри блока catch. Следующая попытка также терпит неудачу, поскольку среда выполнения не любит встречаться с ожиданием после исключения:
private static async Task<T> Retry<T>(Func<T> func, int retryCount)
{
while (true)
{
try
{
var result = await TaskEx.Run(func);
return result;
}
catch
{
if (retryCount == 0)
throw;
retryCount--;
}
}
}
Что касается запроса пользователя, вы можете изменить Retry для вызова функции, которая запрашивает пользователя, и возвращает задачу через TaskCompletionSource для запуска следующего шага, когда пользователь отвечает, например:
private static Task<bool> AskUser()
{
var tcs = new TaskCompletionSource<bool>();
Task.Factory.StartNew(() =>
{
Console.WriteLine(@"Error Occured, continue? Y\N");
var response = Console.ReadKey();
tcs.SetResult(response.KeyChar=='y');
});
return tcs.Task;
}
private static Task<T> RetryAsk<T>(Func<T> func, int retryCount, TaskCompletionSource<T> tcs = null)
{
if (tcs == null)
tcs = new TaskCompletionSource<T>();
Task.Factory.StartNew(func).ContinueWith(_original =>
{
if (_original.IsFaulted)
{
if (retryCount == 0)
tcs.SetException(_original.Exception.InnerExceptions);
else
AskUser().ContinueWith(t =>
{
if (t.Result)
RetryAsk(func, retryCount - 1, tcs);
});
}
else
tcs.SetResult(_original.Result);
});
return tcs.Task;
}
Со всеми продолжениями вы можете понять, почему асинхронная версия Retry настолько желательна.
UPDATE:
В Visual Studio 2012 Beta работают следующие две версии:
Версия с циклом while:
private static async Task<T> Retry<T>(Func<T> func, int retryCount)
{
while (true)
{
try
{
var result = await Task.Run(func);
return result;
}
catch
{
if (retryCount == 0)
throw;
retryCount--;
}
}
}
и рекурсивная версия:
private static async Task<T> Retry<T>(Func<T> func, int retryCount)
{
try
{
var result = await Task.Run(func);
return result;
}
catch
{
if (retryCount == 0)
throw;
}
return await Retry(func, --retryCount);
}