如何使用 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;
我已经开始学习函数式编程,虽然链接方法在正常情况下看起来很棒(在我看来),但在处理 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;