Task.Factory.StartNew - ожидание и возврат результата
Как запустить пачку фоновых потоков и дождаться любой первый с возвратом результата ?
Пробовал так и ещё различным десятком способов после гугла, в том числе и без лямбды.
Task task = Task.Factory.StartNew(async () =>
{
AnalyseInfo analyseInfo = await myAnalyzer.AnalyseAsync();
_analyseInfos.Add(analyseInfo);
});
Task<AnalyseInfo> task = Task<AnalyseInfo>.Factory.StartNew(async () =>
{
return await myAnalyzer.AnalyseAsync();
});
Task<Task<AnalyseInfo>> task = Task<Task<AnalyseInfo>>.Factory.StartNew(async () =>
{
return await myAnalyzer.AnalyseAsync();
});
Но не удалось получить желаемого результата.
Ожидать хотел любую завершившившуюся задачу с помощью Task.WaitAny
// Дождаться завершения любой задачи
int numTask = Task.WaitAny(_tasks.ToArray());
Все попытки заканчивались тем, что задача получала статус завершения, до завершения..
Ответы (2 шт):
Task.Factory.StartNew создаёт новую задачу оборачивая возвращаемое значение вне зависимости от этого самого значения.
Поэтому, когда вы пишете Task.Factory.StartNew(async …), вы получаете Task<Task<…>> - задачу, значение которой тоже задача.
Рассмотрим асинхронный метод подробнее:
async () {
// 1
await Bar();
// 2
}
Если его вызвать, то он вернёт управление после первого нехолостого оператора await, то есть строчка 1 и вызов Bar будут выполнены синхронно, а ожидание Bar и строчка 2 - асинхронно. Когда вы оборачиваете такой метод в задачу - строчка 1 и вызов Bar выполняются во внешней задаче, а ожидание Bar и строчка 2 - во внутренней.
Именно потому у вас и получалось, что задача завершается слишком рано: вы ждали внешнюю задачу.
Как от этого избавиться? Очень просто: используя Unwrap!
Task<AnalyseInfo> t = Task.Factory.StartNew(async () =>
{
return await myAnalyzer.AnalyseAsync();
}).Unwrap();
В этом методе нет никакой магии, если бы его не было - его было бы нетрудно написать самостоятельно:
async Task<T> Unwrap<T>(Task<Task<T>> task) {
return await await task;
}
Второй вариант - можно использовать Task.Run, в отличии от StartNew у него есть специальная перегрузка для асинхронных методов, которая вызывает Unwrap внутри:
Task<AnalyseInfo> t = Task.Run(async () =>
{
return await myAnalyzer.AnalyseAsync();
});
Наконец, третий вариант - вообще не выносить асинхронный метод в отдельный поток, а просто вызвать. Этот вариант не универсален, но обычно работает:
Task<AnalyseInfo> t = myAnalyzer.AnalyseAsync();
Хоть и ответ уже дан выше, попробую внести свои 5 копеек с таким примером:
var longTask = Task.Delay(10000);
var shortTask = Task.Delay(1000);
var tasks = new[] { longTask, shortTask };
var anyTask = await Task.WhenAny(tasks);
Console.WriteLine(anyTask.Status); //RanToCompeltion
Console.WriteLine(shortTask.Status); //RanToCompletion
Console.WriteLine(longTask.Status); //WaitingForActivation
Console.WriteLine(anyTask == shortTask); //True
В таком примере не используется TaskFactory для создания, а используется напрямую ваш таск (что также убирает лишний async и создание лишних стейт-машин для этого таска). В вашем примере в вопросе не хватило кейворда await перед Task.WhenAny, чтобы задача действительно ожидалась.