如何访问调用异步方法时可用的变量?

How to access a variable that was available when an async method was called?

动物名称是从 API 中获取的,如果找不到该动物,可能会 return 404。但是为了正确记录错误,我们需要访问动物的国家。可能吗?我读过 something from a guy called Stephen Cleary 让我觉得 lambda 是可能的,但我找不到任何东西。

var gettingNames = new List<Task<string>>();

foreach (var animal in animals)
{
    gettingNames.Add(this.zooApi.GetNameAsync(animal));
}

try
{
    await Task.WhenAll(gettingNames);
}
catch (Exception e)
{
    var exception = gettingNames.Where(task => task.IsFaulted)
        .SelectMany(x => x.Exception.InnerExceptions).First();

    this.logger.LogError("The animal name from {Country} was not found",
        animal.Country); // This is the goal
}

解决此问题的一种方法是将每个 Animal 投影到一个 Task,它包含比裸名或裸错误更多的信息。例如,您可以将其投影到包含三部分信息的 Task<ValueTuple<Animal, string, Exception>>:动物、来自 zooApi 的动物学名以及调用 zooApi.GetScientificNameAsync 时可能发生的错误方法。

执行此投影的最简单方法是 LINQ Select 运算符:

List<Task<(Animal, string, Exception)>> tasks = animals.Select(async animal =>
{
    try
    {
        return (animal, await this.zooApi.GetScientificNameAsync(animal),
            (Exception)null);
    }
    catch (Exception ex)
    {
        return (animal, null, ex);
    }
}).ToList();

(Animal, string, Exception)[] results = await Task.WhenAll(tasks);

foreach (var (animal, scientificName, error) in results)
{
    if (error != null)
        this.logger.LogError(error,
            $"The {animal.Name} from {animal.Country} was not found");
}

你几乎成功了。 :)

您需要 Dictionary<Task<string>, string> 结构而不是 List<Task<string>>

static async Task Main()
{
    var taskInputMapping = new Dictionary<Task<string>, string>();
    var inputs = new[] { "input", "fault", "error", "test"};
    foreach (var input in inputs)
    {
        taskInputMapping.Add(DelayEcho(input), input);
    }

    try
    {
        await Task.WhenAll(taskInputMapping.Keys);
    }
    catch
    {
        foreach (var pair in taskInputMapping.Where(t => t.Key.IsFaulted))
        {
            Console.WriteLine($"{pair.Value}: {pair.Key.Exception?.GetType().Name}");
        }
    }
}

static readonly ImmutableArray<string> wrongInputs = 
    ImmutableArray.Create("error", "fault");
static async Task<string> DelayEcho(string input)
{
    if (wrongInputs.Contains(input)) throw new ArgumentException();
    await Task.Delay(10);
    return input;
}
  • taskInputMapping.Add(DelayEcho(input), input):将输入存储在任务本身旁边
  • taskInputMapping.Where(t => t.Key.IsFaulted):遍历出错的任务
  • $"{pair.Value}: {pair.Key.Exception?.GetType().Name}":检索输入+相关错误

我综合了这些答案得出了这个:

var tasks = animals.Select(async animal =>
{
    try
    {
        return await this.zooApi.GetNameAsync(animal);
    }
    catch (Exception ex)
    {
        this.logger.LogError(error,
            $"The {animal.Name} from {animal.Country} was not found");

        return null;
    }
});

var results = await Task.WhenAll(tasks);

foreach (var name in results.Where(x => x != null))...