Task.Any () Вызовите задачу дважды - PullRequest
2 голосов
/ 24 января 2020

Я запускаю задачу, как показано ниже.

var tasks = from job in jobs select ProcessFile(job);

где ProcessFile - это метод, который возвращает bool Task

private async Task<bool> ProcessFile(Job job)

Теперь странная вещь заключается в том, что я хочу проверить, есть ли какие-либо из результат задачи был верным или нет, как показано ниже.

 await Task.WhenAll(tasks);    
 var isTrueForAny =tasks.Any(x => x.Result == true);

Но в этот момент мой метод ProcessFile (job) вызывается снова, хотя у меня есть только одна работа. Не могли бы вы, ребята, помочь мне понять, в чем причина?

Ответы [ 2 ]

4 голосов
/ 24 января 2020

Причина такого поведения: Отложенное выполнение .

Отложенное выполнение означает, что вычисление выражения задерживается до тех пор, пока его реализованное значение не станет фактически требуется.

Давайте рассмотрим шаг за шагом:

 var tasks = from job in jobs select ProcessFile(job);

См. значение переменной tasks как запрос sql. Он будет выполняться каждый раз, когда вы пытаетесь использовать переменную tasks в тех местах, где требуется значение этого запроса.

Первый раз, когда «запрос» будет выполнен в ожидании выполнения этих задач.

await Task.WhenAll(tasks);    

И второй раз, когда вы перебираете результат этого query. Однако не все задачи будут выполняться дважды, так как Any перестанет выполнять итерации, как только будет найдено первое совпадающее задание.

 var isTrueForAny =tasks.Any(x => x.Result == true);

Посмотрите на Deferred Execution* Понятие 1030 * в LINQ.

Для решения проблемы необходимо принудительно немедленное выполнение . Чтобы принудительно выполнить запрос, который выполняет не создавая одноэлементное значение, вы можете вызвать метод ToList, метод ToDictionary или метод ToArray для запроса или переменной запроса. Но это будет проблематично c, поскольку вы проецируете Task объект из вашей коллекции. Вы можете обойти эту проблему, просто создав метод расширения ToListAsync.

. Или для решения проблемы вам просто нужно изменить код таким образом, чтобы вы перебирали коллекцию только один раз. (как @Johnathan уже предложил вам в своем ответе)

3 голосов
/ 24 января 2020
bool[] results = await Task.WhenAll(tasks);    
bool isTrueForAny = results.Any(b => b);

Ожидание Task.WhenAll разворачивает результаты задачи в массив, который можно проверить с помощью Any().

Вызов Any() непосредственно на IEnumerable вызовет повторный вызов итератора запустить, поэтому ProcessFile вызывается снова.

...