【问题标题】:Managing Tasks with different outputs管理具有不同输出的任务
【发布时间】:2020-10-13 23:44:10
【问题描述】:

我有一个 .NET Core 2.1 项目,它有一个 BackgroundService,我希望它负责处理记录一组不同任务的结果,这些任务可以返回不同的值。我想将他们的所有输出分组到一个任务管理器类中以记录他们的输出。是否可以有一个 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(...)

如果它们返回不同的类型,我是否可以做 tasks.Add(...) 并我将它们添加到 List&lt;Task&lt;object&gt;&gt; 时很模糊。我不认为这是可能的。我怎样才能实现这样的目标?

最终,我想在 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());
    }
}

【问题讨论】:

  • value 需要做什么?
  • edit 发帖澄清您到底想要实现什么 - _logger.LogInformation(/$"Log task value {value}") 显然是一种解决方案,但我怀疑您是否对此感兴趣。还要确保澄清为什么它不是 stackoverflow.com/questions/17197699/… 的重复项

标签: c# .net-core async-await type-conversion task-parallel-library


【解决方案1】:

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

    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")))());

【讨论】:

  • 我不需要将async () =&gt; ... 包裹在Task.Run() 中吗?因此也不需要await & async
  • 不,不需要使用另一个线程,你想要的只是转换完成线程的结果。
  • 我认为 lambda 表达式 async () =&gt; 不允许在 .Add() 中转换为 Task&lt;object&gt;,因为它们不是委托类型。
  • 什么意思,是委托类型。已经 4 天了,只要输入并尝试一下,哈哈
  • 我确实尝试过这个,而且很长一段时间:Cannot convert lambda expression to type Task&lt;object&gt; because it is not a delegate type. :]
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    如果你经常这样做,你可以使用下面的扩展方法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的answer,只是方便一点。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-06-21
      • 2021-09-21
      • 1970-01-01
      • 2021-08-02
      • 2014-12-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多