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

时间:2015-08-20 07:46:50

标签: c# .net async-await fluent

异步操作似乎不适合我喜欢编写的流畅界面。如何将Asynchrony与Fluent结合使用?

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

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

必须有更好的方法。

7 个答案:

答案 0 :(得分:11)

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

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

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

这样的事情:

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

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

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

答案 1 :(得分: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);
    }
}

答案 2 :(得分: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 :(得分:3)

您可以添加一个扩展方法重载,它将class ArticleController { private AsyncMysqlConnection $connection; public async function viewAction(int $articleId): Awaitable<void> { $this->connection = await AsyncMysqlClient::connect( /* connection data */ ); $article = await $this->getArticleData($articleId); } public async function getArticleData(int $id): Awaitable<?Vector> { $articleDataQuery = await $this->connection->queryf("SELECT * FROM articles WHERE id %=d", $id); if($articleDataQuery instanceof AsyncMysqlQueryErrorResult) { throw new Exception("Error on getting data: ".$articleDataQuery->mysql_error()); } // Considering that $id represents a unique id in your database, then // you are going to get only one row from your database query // so you return the first (and only) row in the query result if($articleDataQuery->numRows() == 1) { return $articleDataQuery->mapRowsTyped()[0]; } return null; } } Task带到您想要链接的任何方法。

Task<T>

所以你可以拨打public static async Task<MyEntity> SecondStepAsync(this Task<MyEntity> entityTask) { return (await entityTask).SecondStepAsync(); }

答案 4 :(得分: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,并返回而不是常规任务。

答案 5 :(得分: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优化异步/同步的组合。给读者的练习! :-)

答案 6 :(得分: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();
}