如何设计流畅的异步操作?

问题描述 投票:9回答:7

异步操作似乎不适合我喜欢编写的流畅接口.Asynchrony如何与Fluent结合使用?


示例:我有两种方法先前返回了MyEntity,但在更改为Async时效果不佳。在我异步他们后,我必须await任务的结果,但我必须为每个步骤添加:

MyEntity Xx = await(await FirstStepAsync()).SecondStepAsync();

一定有更好的方法。

c# .net async-await fluent
7个回答
6
投票

处理延续的一些答案是忘记了从每种方法返回的具体实例上的流畅工作。

我已经为您编写了一个示例实现。异步工作将在调用任何DoX方法时立即开始。

public class AsyncFluent
{
    /// Gets the task representing the fluent work.
    public Task Task { get; private set; }

    public AsyncFluent()
    {
        // The entry point for the async work.
        // Spin up a completed task to start with so that we dont have to do null checks    
        this.Task = Task.FromResult<int>(0);
    }

    /// Does A and returns the `this` current fluent instance.
    public AsyncFluent DoA()
    {
        QueueWork(DoAInternal);
        return this;
    }

    /// Does B and returns the `this` current fluent instance.
    public AsyncFluent DoB(bool flag)
    {
        QueueWork(() => DoBInternal(flag));
        return this;
    }

    /// Synchronously perform the work for method A.
    private void DoAInternal()
    {
        // do the work for method A
    }

    /// Synchronously perform the work for method B.
    private void DoBInternal(bool flag)
    {
        // do the work for method B
    }

    /// Queues up asynchronous work by an `Action`.
    private void QueueWork(Action work)
    {
        // queue up the work
        this.Task = this.Task.ContinueWith<AsyncFluent>(task =>
            {
                work();
                return this;
            }, TaskContinuationOptions.OnlyOnRanToCompletion);
    }
}

11
投票

更好的方法是延迟执行类似于LINQ。

您可以使用许多实际上没有做任何事情的方法,只需设置一些选项或存储一些逻辑。最后有一些方法可以实际执行之前存储的所有其他逻辑。

这样,只需要几个方法就可以使用async,并且在每个链的末尾只使用一个方法。

像这样的东西:

var myEntity = await StartChain().StoreSomeLogic().StoreSomeOtherLogic().ExecuteAsync()

例如,新异步MongoDB C#驱动程序的工作原理如下:

var results = await collection.Find(...).Project(...).Skip(...).Sort(...).ToListAsync();

4
投票

其中一个选项是声明并使用以下通用扩展方法:

public static TR Pipe<T, TR>(this T target, Func<T, TR> func) =>
    func(target);

public static async Task<TR> PipeAsync<T, TR>(this Task<T> target, Func<T, TR> func) =>
    func(await target);

public static async Task<TR> PipeAsync<T, TR>(this Task<T> target, Func<T, Task<TR>> func) =>
    await func(await target);

这些实用程序允许以这种方式表示异步调用链:

MyEntity Xx = await FirstStepAsync()
    .PipeAsync(async firstResult => await firstResult.SecondStepAsync())
    .PipeAsync(async secondResult => await secondResult.ThirdStepAsync());

结果代码可能看起来更冗长,但扩展链更容易,因为没有嵌套括号。


3
投票

你可以添加一个扩展方法重载,它将TaskTask<T>带到你想要可链接的任何方法。

public static async Task<MyEntity> SecondStepAsync(this Task<MyEntity> entityTask)
{
    return (await entityTask).SecondStepAsync();
}

所以你可以打电话给await FirstStepAsync().SecondStepAsync()


2
投票

await基本上是ContinueWith的简写,Task对象的方法(我在这里简化,但这是基本概念)。如果您正在尝试构建流畅的语法,请考虑使用接收任务的扩展方法并使用ContinueWith链接它们:

public Task FirstStep()
{
     return Task.Run(/* whatever */);
}

public static Task SecondStep (this Task previousStep)
{
    return previousStep.ContinueWith(task => { /* whatver */  };
}

现在你可以打电话给await FirstStep().SecondStep(),等待最终的结果。每种方法基本上都会添加另一个ContinueWith步骤。

如果您想使其更安全,请从Task继承MyFluentTask,并返回该而不是常规Task。


0
投票

将您的流畅方法重写为此表单 - 即,将它们作为扩展方法没有意义:

        public static async Task<ResultType> Transform(SourceType original)
        {
            // some async work 
            var transformed = await DoSomething(original)
            return transformed;
        }

提供这些通用的扩展方法(与@Gennadii Saltyshchak的PipeSync方法几乎相同,但我发现“Then”的名字更简洁):

        public static async Task<T2> Then<T1, T2>(this T1 first, Func<T1, Task<T2>> second)
        {
            return await second(first).ConfigureAwait(false);
        }

        public static async Task<T2> Then<T1, T2>(this Task<T1> first, Func<T1, Task<T2>> second)
        {
            return await second(await first.ConfigureAwait(false)).ConfigureAwait(false);
        }

        public static async Task<T2> Then<T1, T2>(this Task<T1> first, Func<T1, T2> second)
        {
            return second(await first.ConfigureAwait(false));
        }

        public static Task<T2> Then<T1, T2>(this T1 first, Func<T1, T2> second)
        {
            return Task.FromResult(second(first));
        }

然后,您可以构建一个像这样的流畅链:

        var processed = await Transform1(source)
            .Then(Transform1)
            .Then(Transform2)
            .Then(Transform3);

由于Then()重载,这适用于异步/同步方法的任何排列。

如果是Transform2接受参数,你需要扩展为lambda:

        var processed = await Transform1(source)
            .Then(Transform1)
            .Then(x => Transform2(x, arg1, arg2))
            .Then(Transform3);

我认为这与您使用当前语言可以获得的流畅异步链接近!

应尽可能使用ValueTask优化异步/同步的组合。为读者练习! :-)


0
投票

另一种选择是实现基本的LINQ运算符以允许LINQ语法。然后你可以这样做:

MyEntity Xx = await
    from f in FirstStepAsync()
    from e in f.SecondStepAsync()
    select e;

你需要的代码是这样的:

public static class Ex
{
    public static Task<TResult> SelectMany<TSource, TResult>(this Task<TSource> source, Func<TSource, Task<TResult>> collectionSelector)
        => source.ContinueWith(t => collectionSelector(t.Result)).Unwrap();

    public static Task<TResult> SelectMany<TSource, TCollection, TResult>(this Task<TSource> source, Func<TSource, Task<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector)
        => source.ContinueWith(t =>
        {
            Task<TCollection> ct = collectionSelector(t.Result);
            return ct.ContinueWith(_ => resultSelector(t.Result, ct.Result));
        }).Unwrap();
}
© www.soinside.com 2019 - 2024. All rights reserved.