如何使用 async/await 链接 .net 中的方法

How to chain methods in .net with async/await

我已经开始学习函数式编程,虽然链接方法在正常情况下看起来很棒(在我看来),但在处理 async/await

时它真的很难看
await (await (await CosmosDbRepository<ApplicationProcess>
    .GetItemAsync(param.ProcessId))
.Historize(() => _analyseFinanciereService.ProcessAsync(), 
    ProcessStepEnum.Application))
.Notify(p => p.GetLastStep());

有什么方法可以消除这种噪音吗?

编辑:

public static async Task<ApplicationProcess> Historize(
this ApplicationProcess process, 
Func<Task> fn, 
ProcessStepEnum stepEnum)
{
    var dateStart = DateTime.UtcNow;
    var error = string.Empty;
    try
    {
        await fn();
        return process;
    }
    …

public static async Task Notify<TResult>(
    this ApplicationProcess process, 
    Func<ApplicationProcess, TResult> fn)
...

Edit2:使用接受任务的扩展方法

await CosmosDbRepository<ApplicationProcess>
    .GetItemAsync(param.ProcessId)
    .HistorizeAsync(() => _analyseFinanciereService.ProcessAsync(), ProcessStepEnum.Application)
    .NotifyAsync(p => p.GetLastStep());

这就是我一直在寻找的东西,即使我对最新的评论感到困惑

我上传的所有代码都是LinqPad query,所以你可以马上试试。

函数式编程有一个monad的概念(不熟悉C#的程序员我强烈建议从提供的link入手)。 C# 任务可能被认为是 monad,据我了解,这正是您所需要的。

为了这个答案的目的,我做了一个简化的例子:

await (await (await A.GetNumber()).DoubleIt()).SquareIt()

其中方法如下(定义为静态只是为了方便):

public static class A
{
    public static Task<int> GetNumber(){return Task.FromResult(3);}
    public static Task<int> DoubleIt(this int input){return Task.FromResult(2 * input);}
    public static Task<int> SquareIt(this int input){return Task.FromResult(input * input);}
}

现在您可以轻松地用一点胶水将它们链接起来,看起来像这样:

public static async Task<TOut> AndThen<TIn, TOut>(this Task<TIn> inputTask, Func<TIn, Task<TOut>> mapping)
{
    var input = await inputTask;
    return (await mapping(input));
}

AndThen 方法的行为与 monadic 绑定完全相同:

await A
     .GetNumber()
     .AndThen(A.DoubleIt)
     .AndThen(A.SquareIt)

更重要的是,C# 具有处理 monad 的良好语法:LINQ 查询理解语法。您只需要定义一个 SelectMany 方法,它适用于您想要的类型(在本例中为任务),您就可以开始了。

下面我实现了最“核心”的 SelectMany 重载(带有额外的 resultSelector),它为您提供了最大的灵活性。简单版本几乎与 AndThen 完全相同(我认为只需重命名即可)。

public static async Task<TOut> SelectMany<TIn, TInterm, TOut>(
   this Task<TIn> inputTask,
   Func<TIn, Task<TInterm>> mapping,
   Func<TIn, TInterm, TOut> resultSelector)
{
    var input = await inputTask;
    return resultSelector(input, await mapping(input));
}

有了它你可以使用语法:

var task = 
    from num in A.GetNumber()
    from doubled in num.DoubleIt()
    from squared in num.SquareIt()
    select $"number: {num} doubled: {doubled}, squared: {squared}";
    
Console.WriteLine(await task);

你得到 number: 3 doubled: 6, squared: 9.

简单的 SelectMany 版本将允许您在最后的 select 行中使用 squared 作为唯一可能的表达式。 “硬核”版本允许您使用任何使用 from 关键字后定义的任何值的表达式。

我试图以一种方式实现它——只有当源任务成功完成时(没有错误),它应该继续执行下一个。这似乎工作正常但尝试思考无论如何这可能会失败:-

return await await sourceTask.ContinueWith(async st => 
            {
               var res = await st; //
               return await contWith(res);
            }, TaskContinuationOptions.NotOnFaulted & TaskContinuationOptions.OnlyOnRanToCompletion;