Go to main content Go to main menu

Avoiding Deadlocks: Building async-friendly validators

By Mia Holmberg Reading time: 2 minutes

In this blog post, we explore ways to improve the built-in validator functionality when working with asynchronous methods. We'll look at how to avoid common pitfalls like deadlocks and thread blocking by building an async-friendly validation approach with proper timeout and cancellation support.

When using a validator (i.e. to validate a block or page property), we usually are implementing the EPiServer.Validation.IValidate<T> interface, but it only has one single method: IEnumerable<ValidationError> Validate(T instance)

So, what should we do when we need to use an asynchronous method in it?

You cannot simply add await cause you will get error:

"The 'await' expression can only be used in a method or lambda marked with the 'async' modifier". 

And you cannot make the method async cause you will get error:

"Method 'Validate' cannot implement method from interface 'EPiServer.Validation.IValidate<EPiServer.Core.PageData>'. Return type should be 'System.Collections.Generic.IEnumerable<EPiServer.Validation.ValidationError>'".

So you would have to do it like this: ...or, is there better solution?

Using GetAwaiter().GetResult() as above in .NET can lead to issues such as deadlocks and performance problems. For example, if the call to the service takes too long, the application will block the current thread until the operation completes. And if the underlying operation has no timeout or a very high timeout, the thread could remain blocked indefinitely, leading to potential application hangs.

What we could do is to create our own async validator infrastructure to reduce the risk of deadlocks and thread-blocking issues, supporting cancellation and timeout. The AsyncValidatator creates a CancellationTokenSource with a timeout of 100,000 milliseconds (100 seconds). This token is used to cancel the operation if it exceeds the timeout (The value should perhaps be set in a config, but it is hardcoded here for the example).

By calling ConfigureAwait(false), it prevents the continuation of the task from capturing the current synchronization context (e.g., UI thread or ASP.NET request context). This avoids potential deadlocks in environments with a synchronization context.

Although GetAwaiter().GetResult() is used, which blocks the thread, the use of ConfigureAwait(false) ensures that the asynchronous operation completes without requiring the original context. This reduces the risk of blocking critical threads like the UI thread.

So now we can use our new async method in our validator by implementing the AsyncValidator. Summary:

So now we have explored how to make validators in Optimizely CMS play nice with async code. Instead of blocking threads and risking deadlocks, we built an async-friendly validator that supports cancellation and timeouts. The key takeaway? Pass that CancellationToken, avoid blocking calls, and make sure your async methods can actually be canceled. Your app (and your future self) will thank you!

 

Reflections:

While the AsyncValidator<T> reduces the risk of deadlocks, it still uses GetAwaiter().GetResult(), which blocks the thread and can lead to performance issues. A fully asynchronous approach would of course be more efficient and hopefully that will be supported in a future version of the IValidationService.

You can also use the similar pattern for Scheduled jobs, see my blog post about it here

We would like to hear what you think about the blog post

Mia Holmberg

Mia Holmberg

Fullstack developer

Read all blog posts by Mia Holmberg