In my previous blog post, I discussed an approach to solve a weak point of builder pattern. In this blog post, I want to continue with a related discussion. The challenge that will be discussed over this blog post is “How can we make builder pattern async?” This is an issue we faced when we decided to migrate our current project to async to increase our throughput. We came to a decision moment, we would either delete the builder classes we had (there were many) or find a way to utilize them in async world.
publicContactBuilder() { contact = new Contact(); } public IContactBuilder WithName(string name) { contact.Name = name; returnthis; }
// Rest is skipped for brevity.
public Contact Build() { returnthis.contact; } }
Of course in an implementation like this, you would not need an async operation, so let’s spice it up a little bit.
Imagine that you would like to construct your contacts in such a way that name and surname should be queried from Linkedin profile, phone and address from Google Contacts. Let’s inject 2 new dependencies to the builder class ILinkedinRepository and IGoogleContactsRepository. For brevity I will skip implementation of these classes, but let’s assume that they will both have an option to read data through username.
public IContactBuilder WithName() { // Network operation that blocks the running thread: var name = this.linkedinRepository.GetName(username); contact.Name = name; returnthis; }
// Rest is skipped for brevity.
public Contact Build() { returnthis.contact; } }
Please note that the concrete implementation always does a network operation and returns this.
Converting the Builder to Async
In async implementation of the same class returning this is not a possible option because we will need to return either a Task for void functions or Task<T> when we want to return a value. Even if we return Task<IContactBuilder> we will break the simplicity of builder pattern. Why? Because our builder and consumer would look like this:
var withNameBuilder = await builder.WithName(); var contact = await withNameBuilder.Build(); } }
You can see where I am trying to go with this. While trying to make the builder pattern async, we are actually sacrificing the advantages of using it.
Proposed Solution(s)
During my initial research for a solution, this StackOverflow question was the only thing that I could find. I started digging up with this starting point to see my options. In the next section you can see the viable options I could come up with.
1- Builder with Parallel Executing Tasks
In the most simple solution, we can use a Queue to start and keep track of new threads per methods within the builder class. Each method action will be a new Task and we can still return this from builder methods. With this approach running thread will never be blocked and only Build() method will be async.
public IContactBuilder WithName() { builderQueue.Enqueue(Task.Run(() => { var name = this.linkedinRepository.GetName(this.username); contact.Name = name; }));
Please note that with every builder method a new Task is being created to handle the operation and running thread just continues execution. Each created Task is running immediately. In the build method, we are awaiting the created tasks.
Pros
All tasks run in parallel
Main thread is not blocked
Faster
Cons
Execution order of tasks cannot be known before hand
If one task fails, other scheduled tasks will still continue to work
2- Builder with ordered Func<Task>
Another variation of this solution can also be necessary. Sometimes when you are building an object, you need to make sure that fields are being assigned in order. Then this variation can be used to do it.
public IContactBuilder WithName() { builderQueue.Enqueue(() => Task.Run(() => { var name = this.linkedinRepository.GetName(this.username); contact.Name = name; }));
returnthis; }
// Other methods are skipped for brevity
publicasync Task<Contact> Build() { Func<Task> startTaskExecution = null; Console.WriteLine(">>> Building contact..."); while (builderQueue.TryDequeue(out startTaskExecution)) { var currentTask = startTaskExecution.Invoke(); await currentTask.ConfigureAwait(false); } Console.WriteLine(">>> Done..."); returnthis.contact; } }
Please note that the builder queue is now of type Func<Task>. So when a new task is being created, it is not immediately running. We still need to invoke this Func to start operation. With this approach, we make sure that builder methods are run in the order they were created.
Please note the builder queue is here again of type Task. For each method in the builder class a new Task is created and it is immediately running. But we also added cancellation checks on the CancellationToken so if any of the operations has a failure, we can stop all other tasks.
Pros
Best of the both worlds from previous examples
Main thread is not blocked
Fast, threads run in parallel
Threads can be cancelled. If one thread gets an exception, other threads can be stopped through cancellation
Cons
More complex. You have to think carefully for cancellation checks and insert where necessary
Execution order of tasks cannot be known before hand
Conclusion
As discussed in the previous sections, keeping builder pattern in an async codebase is difficult. If you are a developer (like me) who thinks builder pattern has a lot of advantages and would like to keep it in your async codebase, then you can adapt one of the solutions discussed here. In all of the solutions, exception handling needs careful decisions because you will be handling a lot of threads and correct decisions need to be made in case one of these threads throws an exception.
Don’t forget that you can also combine one of these solutions with Chain Builders which was mentioned in my previous blog post.
Please let me know what you think in the comments below. I hope you find this post helpful. Example code related to this topic is available in blog-code-examples or in the repl.it links that I have provided in previous sections.