Razorlight: async/await

Created on 11 Feb 2017  路  12Comments  路  Source: toddams/RazorLight

Hi,

This library looks really cool. I wanted to ask if there was a specific reason why you used .GetAwaiter().GetResult() instead of the async/await pattern?

I thought this could cause potential deadlocks or at least it is not leveraging the potential performance gain from running IO bound tasks asynchronously.

Was there a reason for it or would you be open to the idea of changing it to async/await?

feature-proposal

Most helpful comment

Actually it's not about Xamarin or WPF, it's how async/await works. To be more specific, when you execute an asynchronous method - it will still run in the main thread and only when it will hit the line with a first "await" statement - then it checks if task is finished, and whether not - it will return a control to a caller (in case of Xamarin/WPF - UI thread, as a result - they are not freezed).

About non-blocking file read: RazorLight reads file only once, then template is created, compiled and put in cache, so next time you call "Parse" on the page with the same key - it will take it from cache.

But you are right, it's a good idea to have it "async/await" all over the place. I will definitely put it in the backlog

All 12 comments

Well, if I make a "Parse" method async with await instead of GetAwaiter().GetResult() - you will notice, that on WPF / Xamarin / WF applications your UI thread will freeze. That will happen because RazorLight does 4 steps when you call a "Parse" method.

Page lookup => Generation Razor page => Compiling and caching => Rendering.

The thing is that first 3 steps are synchronous and the most time-consuming step - is compilation which is performed by Roslyn. And it has no asynchronous api. So there were 2 options:

  1. Wrap all previous steps with Task.Run() not to block a calling thread and await the last step, which is asynchronous from the box
  2. Block last step and wait for it's result and provide only synchronous API for users.

I decided not to do pseudo-asynchronousness with Task.Run. About deadlocks: so far there were no issues regarding deadlocks or smth, but I see a potential problem - I totally forgot to use ConfigureAwait(false) on some places, and I will fix that asap.

If you have some advises how to do things better - fell free to share your thoughts with me, I will really appreciate that. Thank you :)

Thanks for your answer. To be honest I am not very familiar with WPF/Xamarin/WF, could you maybe explain to me why async/await would freeze the UI thread there?

We are trying to use RazorLight from an ASP.NET web application and I think it would definitely benefit there from non-blocking IO operations. For example when the template files get read from the file system for the first time it would be nice to have it async or other operations which might similarly benefit from async (don't know what else there is in RazorLight now).

I think if you would make RazorLight entirely async/await then the code would be free of specific platform limitations and it could be used in ASP.NET with async/await and in other places it could still be used in a synchronous way:

string result = engine.ParseAsync("Test.cshtml", model).GetAwaiter().GetResult();

That way you defer the choice to the caller and can keep your library async/await, which is probably a good thing as a default. What do you think?

Actually it's not about Xamarin or WPF, it's how async/await works. To be more specific, when you execute an asynchronous method - it will still run in the main thread and only when it will hit the line with a first "await" statement - then it checks if task is finished, and whether not - it will return a control to a caller (in case of Xamarin/WPF - UI thread, as a result - they are not freezed).

About non-blocking file read: RazorLight reads file only once, then template is created, compiled and put in cache, so next time you call "Parse" on the page with the same key - it will take it from cache.

But you are right, it's a good idea to have it "async/await" all over the place. I will definitely put it in the backlog

Cool, I'd be happy to help and prepare a pull request if you like. I wouldn't want to just go ahead and start making changes, so if you like to discuss such a change with me in more detail and agree on the scope then I'd be happy to do the actual work.

Thank you for your efforts, I appreciate that.

I'm planning to do a little bit of refactoring, as I'm seeing a bit of redundant compexity at some parts of the project. After that let's return to this conversation again and discuss the details, alrighty?)

Yeah sure, I understand that. Keep me posted here and thank you!

Hi All,
I have a problem with continue running my method on nested Tack.Run => Task.Run of ASP.NET app
I also reviewed the code and found .GetAwaiter().GetResult()
So, my flow is in deadlock.

Question: when you plan to release a version with some hot fix or any other solution of it?

Sorry, but I think this issue is a bug and not a new feature !

@FanMix The fact that I missed to add configureAwait(false) that causes deadlocks - that's a bug indeed.

async/await all over the place, so you can use it like
CSharp var template = await engine.Parse("key")
Is a new feature

@FanMix
Can you please share me your code that leads to deadlock? I would help me a lot, thank you!

@toddams From the master himself. That article helps to understand async/await changes in .NET Core (for e.g. there is no sync context anymore).

Implemented in 2.0

Was this page helpful?
0 / 5 - 0 ratings