管理具有不同输出的任务

Managing Tasks with different outputs

我有一个 .NET Core 2.1 项目,它有一个 BackgroundService,我希望它的职责只是处理记录来自一组不同任务的结果,这些任务可以 return 不同的值。我想将他们的所有输出分组到任务管理器 class 中以记录他们的输出。是否有可能有一个 List<Task> 将包含来自这些异步方法的所有 Task 对象?

我不想为每个要 await 的方法设置多个 Task 字段。我宁愿将它们放入某种 List 中,因为可能会有比我希望此管理器管理的这三种异步方法更多的方法。

我正在考虑做类似的事情:

public class MyTaskManager : BackgroundService
{
    private readonly ILogger<MyTaskManager> _logger;
    private APIInvoker _invoker;

    public MyTaskManager (ILogger<MyTaskManager> logger, APIInvoker invoker)
    {
        _logger = logger;
        _invoker= invoker;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        List<Task<object>> tasks = new List<Task<object>>();

        tasks.Add(_invoker.GetImportWarningsAsync("1"));
        tasks.Add(_invoker.GetImportErrorsAsync("2"));
        tasks.Add(_invoker.GetImportStatusAsync("3"));
    }

其中GetImportWarningsAsyncGetImportErrorsAsyncGetImportStatusAsync定义为:

internal async Task<string> GetImportWarningsAsync(...)
internal async Task<string> GetImportErrorsAsync(...)
internal async Task<ImportResponse> GetImportLeadStatusAsync(...)

如果它们 return 不同类型,我是否可以做 tasks.Add(...) 我不清楚,我正在将它们添加到 List<Task<object>>。我不认为这是可能的。我怎样才能实现这样的目标?

最终,我想 运行 在 tasks 中的每个 Task 中的任何一个执行时都使用一个方法。

例如

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    List<Task<object>> tasks = new List<Task<object>>();

    tasks.Add(_invoker.GetImportWarningsAsync("1"));
    tasks.Add(_invoker.GetImportErrorsAsync("2"));
    tasks.Add(_invoker.GetImportStatusAsync("3"));


    Task<object> finishedTask = await Task.WhenAny(tasks);
    tasks.Remove(finishedTask);

    HandleTask(finishedTask, await finishedTask);
}

private void HandleTask(Task task, object value)
{
    if (value is ImportResponse)
    {
        _logger.LogInformation((value as ImportResponse).someProp); // Log something
    }
    else
    {
        // Any other object type will be logged here - In this case string.
        _logger.LogInformation(value.ToString());
    }
}

任务不像那样协变,但没有什么能阻止您根据需要自行转换结果:

    var tasks = new List<Task<object>>();

    tasks.Add(((Func<Task<object>>)(async () => (object)await _invoker.GetImportWarningsAsync("1")))());
    tasks.Add(((Func<Task<object>>)(async () => (object)await _invoker.GetImportErrorsAsync("2")))());
    tasks.Add(((Func<Task<object>>)(async () => (object)await _invoker.GetImportStatusAsync("3")))());

这很可能不是最好的方法,但它确实按预期工作。

public static class TaskExtensions
{
    public static async Task<(T1, T2)> WhenAll<T1, T2>(Task<T1> t1, Task<T2> t2)
    {
        return (await t1, await t2);
    }

    public static async Task<(T1, T2, T3)> WhenAll<T1, T2, T3>(Task<T1> t1, Task<T2> t2, Task<T3> t3)
    {
        return (await t1, await t2, await t3);
    }

    public static async Task<(T1, T2, T3, T4)> WhenAll<T1, T2, T3, T4>(Task<T1> t1, Task<T2> t2, Task<T3> t3, Task<T4> t4)
    {
        return (await t1, await t2, await t3, await t4);
    }
    
    //etc.
}

这里我们利用了ValueTuple.

用法示例:

var (warnings, errors, status) = await TaskExtensions.WhenAll(
   _invoker.GetImportWarningsAsync("1"),
   _invoker.GetImportErrorsAsync("2"),
   _invoker.GetImportStatusAsync("3") 
);

这里我们利用了 C# 7 的解构功能。

解构变量的类型:

  • warnings: string
  • errors: string
  • status: ImportResponse

如果你经常这样做,你可以使用如下所示的扩展方法ToObjectAsync

public static async Task<object> ToObjectAsync<T>(this Task<T> task)
{
    return await task;
}

用法示例:

var tasks = new List<Task<object>>();

tasks.Add(_invoker.GetImportWarningsAsync("1").ToObjectAsync());
tasks.Add(_invoker.GetImportErrorsAsync("2").ToObjectAsync());
tasks.Add(_invoker.GetImportStatusAsync("3").ToObjectAsync());

本质上等同于Blindy的,只是更方便一些。