это повысит производительность и удобочитаемость кода
Нет, определенно не будет :) Более того, вы столкнулись с некоторыми проблемами с этим кодом, такими как избыточная блокировка или проглатывание исключений, но на самом деле не выполняете код параллельно.
Похоже, вы хотите получить максимально быстрый вызов для вашего Action
, используя какие-то прокси-объекты. Вам нужно запустить Tasks
асинхронно, а не .Wait()
.
Что-то подобное может быть полезным для вас:
public static class TaskExtensions
{
public static TReturn ParallelSelectReturnFastest<TPoolObject, TReturn>(this TPoolObject[] pool,
Func<TPoolObject, CancellationToken, TReturn> func,
int? timeout = null)
{
var ctx = new CancellationTokenSource();
// for every object in pool schedule a task
Task<TReturn>[] tasks = pool
.Select(poolObject =>
{
ctx.Token.ThrowIfCancellationRequested();
return Task.Factory.StartNew(() => func(poolObject, ctx.Token), ctx.Token);
})
.ToArray();
// not sure if Cast is actually needed,
// just to get rid of co-variant array conversion
int firstCompletedIndex = timeout.HasValue
? Task.WaitAny(tasks.Cast<Task>().ToArray(), timeout.Value, ctx.Token)
: Task.WaitAny(tasks.Cast<Task>().ToArray(), ctx.Token);
// we need to cancel token to avoid unnecessary work to be done
ctx.Cancel();
if (firstCompletedIndex == -1) // no objects in pool managed to complete action in time
throw new NotImplementedException(); // custom exception goes here
return tasks[firstCompletedIndex].Result;
}
}
Теперь вы можете использовать этот метод расширения для вызова определенного действия в любом пуле объектов и получения первого выполненного результата:
var pool = new[] { 1, 2, 3, 4, 5 };
var result = pool.ParallelSelectReturnFastest((x, token) => {
Thread.Sleep(x * 200);
token.ThrowIfCancellationRequested();
Console.WriteLine("calculate");
return x * x;
}, 100);
Console.WriteLine(result);
Выводит:
высчитывает
1
Поскольку первая задача завершит работу за 200 мс, верните ее, а все другие задачи будут отменены с помощью токена отмены.
В вашем случае это будет что-то вроде:
var actionResponse = proxiesList.ParallelSelectReturnFastest((proxy, token) => {
token.ThrowIfCancellationRequested();
return proxy.SomeAction();
});
Некоторые вещи, которые стоит упомянуть:
- Убедитесь, что ваши действия безопасны. Вы не можете полагаться на то, сколько из них действительно придет к фактическому исполнению вашего действия. Если это действие
CreateItem
, то вы можете создать множество элементов с помощью различных прокси
- Это не может гарантировать, что вы будете выполнять все эти действия параллельно, потому что выбор времени выполнения задач зависит от TPL
- Я реализовал старомодным способом TPL, потому что ваш первоначальный вопрос содержал его. Если возможно, вам нужно переключиться на async / await - в этом случае ваш
Func
будет возвращать задачи, и вам нужно будет использовать await Task.WhenAny(tasks)
вместо Task.WaitAny()