提问人:jmn 提问时间:10/2/2021 最后编辑:jmn 更新时间:11/18/2023 访问量:208
避免在使用 Task.WhenAll() 等待的任务分配时检查条件两次
Avoid checking condition twice when assigning from task awaited with Task.WhenAll()
问:
有没有更好的方法来编写这个异步代码(例如,这样我就不需要重复两次)?我想避免在这里使用。if (myCondition)
Task.Run
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;
}
答:
0赞
Theodor Zoulias
10/2/2021
#1
一种方法是有两个列表,一个任务列表和一个操作列表。这些操作将在完成所有任务后按顺序调用,并将分配对象的属性。例:result
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());
这样,您就不需要将每个变量存储在单独的变量中,因为每个 lambda 表达式都会在块的内部范围内捕获变量。当 调用 时,将完成,因此不会阻塞。Task
task
if
Action
task
task.Result
只是为了好玩:如果你想变得花哨,你可以将这个“并行对象初始化”功能封装在一个类中,它将同时调用所有异步方法,然后创建一个新对象并按顺序分配其每个属性的值:ObjectInitializer
public class ObjectInitializer<TObject> where TObject : new()
{
private readonly List<Func<Task<Action<TObject>>>> _functions = new();
public void Add<TProperty>(Func<Task<TProperty>> valueGetter,
Action<TObject, TProperty> propertySetter)
{
_functions.Add(async () =>
{
TProperty value = await valueGetter();
return instance => propertySetter(instance, value);
});
}
public async Task<TObject> Run(object arg = null)
{
var getterTasks = _functions.Select(f => f());
Action<TObject>[] setters = await Task.WhenAll(getterTasks);
TObject instance = new();
Array.ForEach(setters, f => f(instance));
return instance;
}
}
使用示例:
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();
评论
0赞
Theodor Zoulias
10/2/2021
@jmn我添加了一种“只是为了好玩”的方法。这是我几周前在这个问题中发布的类似类的修改版本。
0赞
GSerg
10/2/2021
为什么你把它做成两个列表,而不是一个元组列表?
0赞
Theodor Zoulias
10/3/2021
@GSerg当然,您可以使用一个列表而不是两个列表,但我不确定这是否会使代码更具可读性或更有效率。
0赞
GSerg
10/3/2021
当然会这样,原因与为什么一个数组 of 比两个数组 和 更易于维护的原因相同。struct {int id, string name}
int[] ids
string[] names
0赞
Theodor Zoulias
10/3/2021
@GSerg嗯,你能发布一个列表的实现作为答案吗,这样我们就可以直观地比较这两个实现?
1赞
Xerillio
10/3/2021
#2
在不知道您的条件正在检查什么的情况下,我想我通常会将该检查移动到与获取 foo 或 bar 实际相关的方法中。这似乎是您的一种方法做得比它应该做的更多的情况。
另一种方法:
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();
}
评论
0赞
Theodor Zoulias
10/3/2021
这是一种有趣的方法,但假设将 和 属性设置为 是可以的。事实可能并非如此。在对象构造过程中,这些属性可能已初始化为非值。foo
bar
null
null
result
1赞
Xerillio
10/3/2021
@TheodorZoulias同意,但如果这是真的,我怀疑存在一些设计缺陷。但这只是基于这个非常假设的例子的猜测。我将调整代码句柄(await fooTask) ?? result.foo
评论
Task.WhenAll()
if (t1 != null) {}
List<(Task<string>, Action<string, result>)>
(getAsync(), (s, r) => {r.foo = s;})
t1
t2
WhenAll
await