Положим, у меня есть ненадёжный сервис, и я хочу получить от него некие данные. У меня есть класс:
private class CrapResult
{
public string Crap1;
public bool IsCrap1Received;
public string Crap2;
public bool IsCrap2Received;
public string Crap3;
public bool IsCrap3Received;
}
Я пытаюсь получить все эти значения, чтобы передать их дальше. Если получить не удаётся, я сохраняю то, что получено, и в следующий раз не запрашиваю, а наступает этот следующий раз после некоей паузы, неважно какой.
Получение делается примерно так:
var tasks = new List<Task>();
Task<string> crap1Task = null;
if (!res.IsCrap1Received)
{
crap1Task = crapService.GetCrap1(session, id,...);
tasks.Add(crap1Task);
}
...
await Task.WhenAll(tasks);
if (crap1Task!=null)
{
res.IsCrap1Received = true;
res.Crap1 = crap1Task.Result;
}
...
При этом, если хоть одна задача из списка tasks выбросит исключение, то и await Task.WhenAll(tasks) моментально прекратит работу и выбросит AggregateException, не дожидаясь, пока остальные задачи выполнятся. Но мне-то надо дождаться всех! Что мне, писать свой собственный метод, который при выбросе исключения будет убирать задачу в состоянии faulted из списка (ещё остаётся вопрос, как её найти), и ещё раз вызовет WhenAll, пока все задачи не завершатся успешно или не выбросят исключение? А неужели такой метод никто не написал до меня? Я знаю про существование метода WaitAll, но он блокирующий и мне как-то не хочется его вызывать из async-метода, или из обычного метода, который выше по стеку вызывает async-метод.
Я чувствую у себя некий пробел в знаниях, но сформулировать его не могу.
что-то я сомневаюсь, что WhenAll сразу же прекратит исполнение, если один из Task бросит исключение, я думаю, что WhenAll будет пытаться выполнить все Tasks независимо от каких-то исключений, попробуй вот так:
Сначала обрабатывать исключения самому. Если набор строк не может содержать какой-либо строки (null или абракадабру или "-1" и т.д., при обработке исключения возвращать эту строку. Если возможны любые строки, заменить Tаsk<string> на Tаsk<(string,bool)> (по bool определять, есть ошибка или нет) или на Tаsk<(string,int)> (по int определять, есть ошибка или нет, если есть какая)или на Tаsk<string[]> (строка [1] — описание ошибки, если она есть, если нет =null).
Re: Дождаться завершения всех задач вне зависимости от исключени
Можно обернуть задачу, которую возвращает Task.WhenAll в свою, и await'ить ее.
Например, как-то так:
class Program
{
static async Task Main(string[] args)
{
var tasks = new Task[10];
var random = new Random();
for (int i = 0; i < tasks.Length; i++)
{
tasks[i] = Task.Factory.StartNew(() =>
{
var n = random.Next(1, 5);
if (n < 2)
{
throw new InvalidOperationException();
}
return n;
});
}
var task = Task.WhenAll(tasks).ContinueWith((t) =>
{
return t.Status == TaskStatus.RanToCompletion;
});
var success = await task;
if (success)
{
Console.WriteLine("All tasks succeded");
}
else
{
for (int i = 0; i < tasks.Length; i++)
{
if (tasks[i].Status != TaskStatus.RanToCompletion)
{
Console.WriteLine($"Task {i} failed.");
}
}
}
}
}
... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Re: Дождаться завершения всех задач вне зависимости от исключени
Здравствуйте, Слава, Вы писали:
С>При этом, если хоть одна задача из списка tasks выбросит исключение, то и await Task.WhenAll(tasks) моментально прекратит работу и выбросит AggregateException, не дожидаясь, пока остальные задачи выполнятся. Но мне-то надо дождаться всех! Что мне, писать свой собственный метод, который при выбросе исключения будет убирать задачу в состоянии faulted из списка (ещё остаётся вопрос, как её найти), и ещё раз вызовет WhenAll, пока все задачи не завершатся успешно или не выбросят исключение? А неужели такой метод никто не написал до меня? Я знаю про существование метода WaitAll, но он блокирующий и мне как-то не хочется его вызывать из async-метода, или из обычного метода, который выше по стеку вызывает async-метод.
С>Я чувствую у себя некий пробел в знаниях, но сформулировать его не могу.
В той постановке как задача описана, я бы предложил решение через собственную обертку так:
async Task GetAllData( ICrapService crapService, string session, int id )
{
var res = new CrapResult();
await Task.WhenAll(
RetrieveAndStoreData( crapService.GetCrap1( session, id ), ( string s ) => res.Crap1 = s, () => res.IsCrap1Received = true ),
RetrieveAndStoreData( crapService.GetCrap2( session, id ), ( string s ) => res.Crap2 = s, () => res.IsCrap2Received = true ),
RetrieveAndStoreData( crapService.GetCrap3( session, id ), ( string s ) => res.Crap3 = s, () => res.IsCrap3Received = true ),
);
}
async Task RetrieveAndStoreData<T>( Task<T> getData, Action<T> setData, Action setFlag )
{
try
{
var res = await getData;
setData( res );
setFlag();
}
catch(Exception ex)
{
// подавляем, как я понял, или что там еще надо сделать для случая "была проблема при загрузке, надо попробовать позже".
}
}
Но я не уверен, что это хорошо в целом. Порефакторить для начала на этом уровне можно так, избавившись от отдельных флажков (которые могут разойтись с реальными данными):
private class ImprovedCrapResult
{
public class BasicCrapResult<T>
{
public T Result { get; set; }
// возможно еще параметры типа успешно/неуспешно, или когда была последняя попытка запроса.
}
public BasicCrapResult<string> Crap1; // null - нет результата, иначе - собственно результат. Или по отдельным полям в BasicCrapResultpublic BasicCrapResult<string> Crap2;
public BasicCrapResult<string> Crap3;
}
А далее надо смотреть выше, нужен ли вообще такой вот своеобразный класс результата и обобщенный запрос всего что на самом деле вместе-то и не нужно, раз может подождать.
А если оно все равно нужно вместе, просто это будут новые попытки, так их стоит вынести в загрузку отдельных частей — чтобы каждая из них пробовала несколько раз с паузами, а потом или возвращала все же результат или уже выкидывала исключение что "все, дальше пытаться бесполезно" — и это будет общая ошибка. И запускать их в параллельс обычным WhenAll.