简体   繁体   English

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

[英]How to initilize an optional Task and Task<T> using C#?

How to optionally create multiple tasks and execute them using await Task.WhenAll(a,b,c);如何选择创建多个任务并使用await Task.WhenAll(a,b,c);执行它们await Task.WhenAll(a,b,c);

I have the following class我有以下课程

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;
    }
}

I want to initialize the task to a default value that does nothing.我想将任务初始化为不执行任何操作的默认值。 As you can see in the above code, the typeFetcher task is initialized using Task.FromResult<int?>(null);正如你在上面的代码中看到的, typeFetcher任务是使用Task.FromResult<int?>(null);初始化的Task.FromResult<int?>(null); and when the given TaskOption implements IProcessType interface, the task is overridden using a custom task.并且当给定的TaskOption实现IProcessType接口时,使用自定义任务覆盖该任务。 The same scenario implies for the subtypeFetcher task.同样的场景也适用于subtypeFetcher任务。

For the mainContent task, I initialized it using a Task.CompletedTask and when Result.Content is not null, I set the mainContent to SetMainContentAsync(Result.Content) .对于mainContent任务,我使用Task.CompletedTask初始化它,当Result.Content不为空时,我将mainContent设置为SetMainContentAsync(Result.Content) Finally, I await all tasks using await Task.WhenAll(typeFetcher, subtypeFetcher, mainContent) .最后,我使用await Task.WhenAll(typeFetcher, subtypeFetcher, mainContent)等待所有任务。

The ASP.NET Core Performance Best Practices states the following ASP.NET Core 性能最佳实践声明如下

Do not : Block asynchronous execution by calling Task.Wait or Task.Result .不要:通过调用Task.WaitTask.Result阻止异步执行。

Since I am using Task.FromResult<int?>(null);因为我使用的是Task.FromResult<int?>(null); to initialize the tasks, I feel that I am violating best practices.初始化任务,我觉得我违反了最佳实践。 I would like to learn the correct approach to initialize an optional task.我想学习初始化可选任务的正确方法。

Question What is the correct approach to initialize an optional task that returns type?问题初始化返回类型的可选任务的正确方法是什么? Also, is initializing a task that does not return anything to Task.CompletedTask acceptable or is there a more correct approach?另外,初始化一个不向Task.CompletedTask返回任何内容的任务是可以接受的还是有更正确的方法?

What you are doing is fine.你在做什么很好。 Task.FromResult is not Task.Result , the two are entirely unrelated. Task.FromResult不是Task.Result ,两者完全无关。

Task.Result is an instance property that blocks the current thread until the task completes, and returns the result of the task. Task.Result是一个实例属性,它阻塞当前线程直到任务完成,并返回任务的结果。

Task.FromResult is a static method that returns a trivial, already completed task. Task.FromResult是一个静态方法,它返回一个微不足道的、已经完成的任务。

First things first: using .Result the way you are is "ok" because you already made sure it's complete by using await Task.WhenAll .首先要做的事情是:以您的方式使用.Result是“确定的”,因为您已经通过使用await Task.WhenAll确保它已完成。 Your code won't be sitting at .Result waiting for the result and blocking the thread.您的代码不会坐在.Result等待结果并阻塞线程。 But I say "ok" in quotes because of the way exceptions are handled.但由于处理异常的方式,我在引号中说“ok”。 When you use .Result , exceptions are always put inside of an AggregateException and you have to look inside to find the real exception.当您使用.Result ,异常总是放在AggregateException ,您必须查看内部才能找到真正的异常。

However, you can still use await with completed tasks, and exceptions are thrown as you'd expect:但是,您仍然可以对已完成的任务使用await ,并且会按照您的预期抛出异常:

Result.TypeId = await typeFetcher;

As to the rest of the question: the best way I find to await multiple, optional tasks is to use a List<Task> and just add the tasks that are needed.至于问题的其余部分:我发现等待多个可选任务的最佳方法是使用List<Task>并只添加所需的任务。

var taskList = new List<Task>();

However, this is complicated by the fact that you need to use the return values from the tasks.但是,由于您需要使用任务的返回值,因此这很复杂。 There are a couple ways to go about it.有几种方法可以解决这个问题。 You can use a list that you await, but also keep a reference to each task itself as you already were.您可以使用等待的列表,但也可以像以前一样保留对每个任务本身的引用。 Initialize the Task to null and only set the value if it's not null .Task初始化为null并且仅在它不是null时才设置该值。 For example:例如:

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;
}

...

But you keeping two references to the same thing is... I don't know.但是你对同一件事保留两个引用是......我不知道。 Seems like it could be done better.好像还可以做得更好。

Another option is to use local functions that set the value and add that to your task list.另一种选择是使用设置值的本地函数并将其添加到您的任务列表中。 That way, you don't have to set the values after awaiting.这样,您不必在等待后设置值。

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;
}

I'm not saying that's any more efficient than keeping two references to each Task .我并不是说这比保留对每个Task两个引用更有效。 Just do what you find least confusing.只做你觉得最不容易混淆的事情。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM