Search code examples
c#.netasync-awaitfluent

How to Design Fluent Async Operations?


Async operations do not seem to play well with fluent interfaces which I prefer to code in. How can Asynchrony be combined with Fluent?


Sample: I have two methods that previously returned a MyEntity but do not play well when change to Async. After I asyncfy them I have to await the result of the tasks, but I have to do that for each step added:

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

There has to be a better way.


Solution

  • Some of the answers that deal with continuations are forgetting that fluent works on concrete instances that are returned from each method.

    I have written a sample implementation for you. The asynchronous work will start immediately on calling any of the DoX methods.

    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);
        }
    }