如何使用C#初始化可选Task和Task <T>?

时间:2019-12-13 20:47:28

标签: c# asynchronous async-await task

如何有选择地创建多个任务并使用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.WaitTask.Result阻止异步执行。

由于我使用Task.FromResult<int?>(null);初始化任务,因此我觉得自己违反了最佳实践。我想学习初始化可选任务的正确方法。

问题是初始化返回类型的可选任务的正确方法是什么?此外,初始化一项不会将任何内容返回到Task.CompletedTask的任务还是可以接受的,还是有更正确的方法?

2 个答案:

答案 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保留两个引用要有效。只要做您最不容易混淆的事情即可。