如何有选择地创建多个任务并使用await Task.WhenAll(a,b,c);
我有以下课程
public class TaskProcessor
{
private ProcessResult Result;
public TaskProcessor(ProcessResult result)
{
Result = result ?? throw new ArgumentNullException(nameof(result));
}
public async Task<ProcessResult> Process(TaskOption option)
{
// First optional task
Task<int?> typeFetcher = Task.FromResult<int?>(null);
if (option is IProcessType t)
{
typeFetcher = t.GetAsync();
}
// Second optional task
Task<int?> subtypeFetcher = Task.FromResult<int?>(null);
if (option is IProcessSubtype st)
{
subtypeFetcher = st.GetAsync();
}
// Third optional task
Task mainContent = Task.CompletedTask;
if(Result.Content != null)
{
mainContent = SetMainContentAsync(Result.Content);
}
await Task.WhenAll(typeFetcher, subtypeFetcher, mainContent);
Result.TypeId = typeFetcher.Result,
Result.SubtypeId = subtypeFetcher.Result;
return result;
}
}
我想将任务初始化为不执行任何操作的默认值。如您在上面的代码中看到的那样,typeFetcher
任务是使用Task.FromResult<int?>(null);
初始化的,并且当给定的TaskOption
实现IProcessType
接口时,该任务将使用自定义任务被覆盖。同样的情况也意味着subtypeFetcher
任务。
对于mainContent
任务,我使用Task.CompletedTask
对其进行了初始化,并且当Result.Content
不为空时,我将mainContent
设置为SetMainContentAsync(Result.Content)
。最后,我使用await Task.WhenAll(typeFetcher, subtypeFetcher, mainContent)
等待所有任务。
ASP.NET Core Performance Best Practices声明以下内容
请勿:通过调用
Task.Wait
或Task.Result
阻止异步执行。
由于我使用Task.FromResult<int?>(null);
初始化任务,因此我觉得自己违反了最佳实践。我想学习初始化可选任务的正确方法。
问题是初始化返回类型的可选任务的正确方法是什么?此外,初始化一项不会将任何内容返回到Task.CompletedTask
的任务还是可以接受的,还是有更正确的方法?
答案 0 :(得分:7)
您在做什么很好。 Task.FromResult
不是Task.Result
,两者是完全无关的。
Task.Result
是一个实例属性,它将阻塞当前线程,直到任务完成,并返回任务的结果。
Task.FromResult
是一个静态方法,它返回一个已经完成的琐碎任务。
答案 1 :(得分:2)
第一件事:使用.Result
是可以的,因为您已经通过使用await Task.WhenAll
确保它已经完成。您的代码不会坐在.Result
上等待结果并阻塞线程。但是由于处理异常的方式,我用引号说“好”。当您使用.Result
时,异常总是放在AggregateException
内,您必须查看内部以查找真正的异常。
但是,您仍然可以将await
用于已完成的任务,并且会按预期抛出异常:
Result.TypeId = await typeFetcher;
关于其余问题:我发现等待多个可选任务的最佳方法是使用List<Task>
并仅添加所需的任务。
var taskList = new List<Task>();
但是,这很复杂,因为您需要使用任务的返回值。有几种解决方法。您可以使用等待列表,也可以像以前一样保留对每个任务本身的引用。将Task
初始化为null
,并且仅在非null
时设置值。例如:
var taskList = new List<Task>();
Task<int?> typeFetcher = null;
if (option is IProcessType t)
{
typeFetcher = t.GetAsync();
taskList.Add(typeFetcher);
}
...
await Task.WhenAll(taskList);
if (typeFetcher != null)
{
Result.TypeId = await typeFetcher;
}
...
但是您保留对同一事物的两个引用是……我不知道。似乎可以做得更好。
另一种选择是使用local functions来设置值并将其添加到任务列表中。这样,您不必在等待后设置值。
public async Task<ProcessResult> Process(TaskOption option)
{
var taskList = new List<Task>();
// First optional task
if (option is IProcessType t)
{
// Declare a local function that sets the value
async Task TypeFetcher()
{
Result.TypeId = await t.GetAsync();
}
// Call it and add the resulting Task to the list
taskList.Add(TypeFetcher());
}
// Second optional task
Task<int?> subtypeFetcher = Task.FromResult<int?>(null);
if (option is IProcessSubtype st)
{
async Task SubtypeFetcher()
{
Result.SubtypeId = await st.GetAsync();
}
taskList.Add(SubtypeFetcher());
}
// Third optional task
if(Result.Content != null)
{
//don't need a local function here since there's no return value
taskList.Add(SetMainContentAsync(Result.Content));
}
await Task.WhenAll(taskList);
//I assume you actually declared and set result somewhere
return result;
}
我并不是说这比对每个Task
保留两个引用要有效。只要做您最不容易混淆的事情即可。