¿Hay una mejor manera de escribir este código asincrónico (por ejemplo, para que no tenga que repetir if (myCondition) dos veces)? Quiero evitar usar Task.Run aquí.
var tasks = new List<Task>(); Task<String> t1 = null; Task<String> t2 = null; if (myCondition) { t1 = getAsync(); tasks.Add(t1); } if (myOtherCondition) { t2 = getAsync2(); tasks.Add(t2); } await Task.WhenAll(tasks) if (myCondition) { result.foo = await t1; } if (myOtherCondition) { result.bar = await t2; }Sin saber realmente cuáles son sus condiciones, creo que normalmente movería esa verificación dentro del método que realmente se relaciona con obtener foo o bar. Parece un caso en el que tu único método hace más de lo que se supone que debe hacer.
Un enfoque diferente:
var fooTask = GetFoo(); var barTask = GetBar(); await Task.WhenAll(new [] { fooTask, barTask }); result.foo = (await fooTask) ?? result.foo; result.bar = (await barTask) ?? result.bar; // ... async Task<string> GetFoo() { if (!myCondition) { return Task.FromResult((string)null); } return await DoHeavyWorkFoo(); } async Task<string> GetBar() { if (!myOtherCondition) { return Task.FromResult((string)null); } return await DoHeavyWorkBar(); }Una forma de hacerlo es tener dos listas, una lista de tareas y una lista de acciones. Las acciones se invocarán secuencialmente después de la finalización de todas las tareas y asignarán las propiedades del objeto de result . Ejemplo:
var tasks = new List<Task>(); var actions = new List<Action>(); var result = new MyClass(); if (myCondition) { var task = getAsync(); tasks.Add(task); actions.Add(() => result.Foo = task.Result); } if (myOtherCondition) { var task = getAsync2(); tasks.Add(task); actions.Add(() => result.Bar = task.Result); } await Task.WhenAll(tasks); actions.ForEach(action => action()); De esta forma, no necesita almacenar cada Task en una variable separada, porque cada expresión lambda captura la variable de la task en el ámbito interno del bloque if . Cuando se invoca la Action , la task se completará y, por lo tanto, la task.Result no se bloqueará.
Solo por diversión : si quiere ser elegante, puede encapsular esta funcionalidad de "inicialización de objetos paralelos" en una clase ObjectInitializer , que invocaría todos los métodos asincrónicos al mismo tiempo, y luego crearía un nuevo objeto y asignaría el valor de cada una de sus propiedades. secuencialmente:
public class ObjectInitializer<TObject> where TObject : new() { private readonly List<Func<object, Task<Action<TObject>>>> _functions = new(); public void Add<TProperty>(Func<object, Task<TProperty>> valueGetter, Action<TObject, TProperty> propertySetter) { _functions.Add(async arg => { TProperty value = await valueGetter(arg); return instance => propertySetter(instance, value); }); } public async Task<TObject> Run(object arg = null) { var getterTasks = _functions.Select(f => f(arg)); Action<TObject>[] setters = await Task.WhenAll(getterTasks); TObject instance = new(); Array.ForEach(setters, f => f(instance)); return instance; } }Ejemplo de uso:
var initializer = new ObjectInitializer<MyClass>(); if (myFooCondition) initializer.Add(_ => GetFooAsync(), (x, v) => x.Foo = v); if (myBarCondition) initializer.Add(_ => GetBarAsync(), (x, v) => x.Bar = v); MyClass result = await initializer.Run();