-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Handling errors in ComponentBase.InvokeAsync #27716
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
@poke thanks for the detailed explanation. I don't remember the exact details, but here are a couple of thoughts:
|
@javiercn First of all, thanks for the quick response. I have been working on a repro now and tried a few things.
Yes, that’s correct. I could for example do
I don’t think I understand this idea. My ideas so far would have been catching the exceptions in all
So this one is really odd. First of all, I still don’t fully understand the synchronization contexts so this is a bit magic to me. I was under the assumption that since the invocation inside the synchronization context is done, the exception could not bubble up since the So I have tried to replace my call above with an However, and this is where it gets really bad, when I raise the event from somewhere else, for example a different HTTP request, then this uncaught exception causes a stack trace like this and actually crashes the application. Both Kestrel and IIS Express are impacted:
So this is actually worse than before where exception was just swallowed and the circuit resulted in an invalid (but still connected) state. This crashes the application completely which might even indicate a bug (or I’m doing something super dumb here). The repro is over at https://github.com/poke/aspnet-blazor-issue27716. You can just clone and
If you use it just like that, it works exactly how I would like to do: You can view the list and add items via the buttons. The list will automatically reload. If you enable the exception and then add an item, the list will also reload but fail and the circuit will disconnect. This is what wasn’t working before making it But now there’s also one more thing in this app, which is an endpoint at I hope I am doing something really stupid here because being able to crash the ASP.NET Core app doesn’t sound like something you should be able to do from a Blazor server component 😨 |
@poke thanks for the additional details and the repro. I took a look at it and the behavior is by design. The problem is that the exception is being raised from "outside" the Blazor synchronization context, and you can't "invokeasync" on to it when the context is not setup, so you end up with a background thread that throws an exception and that's why the app gets terminated. In other words, the synchronization context lets you get back to "where you were" but it doesn't let you get back to where you setup the callback "from somewhere else". My take is that you avoid this pattern all together since it is bound to give you grief and instead wrap the component into a separate component that handles the event notification and passes down a task to the components below as a parameter or as a cascading parameter. A great example of one of this type of implementation is |
This issue has been resolved and has not had any activity for 1 day. It will be closed for housekeeping purposes. See our Issue Management Policies for more information. |
@javiercn Thank you for your answer (and sorry for the late response)!
Hm, guess I misunderstood this then. I was hoping that
That’s a real shame though since I found events to be a very natural way to do this, and I was actually happy to get back some use case for them considering that they have mostly disappeared elsewhere. I guess, if I were to do error handling on each call site, I would probably be fine continuing this approach too. I’ve took another close look at how the authentication state handles this and it appears that it mostly solves this by moving the actual awaiting of the task into the component rendering process (either within the lifecycle events or within the Razor code itself). That way, the error is thrown within the context and exceptions will bubble up with the default error handling. But I think that the fact that it’s a cascading parameter does not directly have an impact here, and I actually think that it complicates the situation in my example for various reasons: You will have to wrap everything in another component hierarchy which will cause problems if you use page-based routing since you will either have to make this “state” globally available through the host/app component, or completely change what the target component for the routing is so that you can add this extra layer. I have an example of this in the cascading-parameter branch of my repo. I also implemented a different approach, moving the await into the lifecycle, in the lifecycle branch where I just set a marker in the event handler and trigger re-rendering which will then cause the data loading within the The latter is likely what I am going to follow with for now since it is the “easiest” way for me to migrate to without having to change the component hierarchy everywhere. Summing this up, there are still two things that I would wish to change here:
Anyway, thank you for your response! And I hope you will actually see my reply given the very aggressive issue management (closing after one day of inactivity after I had to wait 6 days too seems a bit harsh 😁). |
@poke thanks for the additional details. I'll try to give some final answers just for completeness.
The exception is thrown from a background thread, so ASP.NET Core request pipeline doesn't get to see the exception and since it's an exception on a background thread, it will crash the host. I think there is also a misunderstanding on how the sync context works and what is capable of doing. The problem is that InvokeAsync only works when you are calling it from within the synchronization context already. It's goal is to make sure that a callback/continuation executes within the same context it was originally present. The issue in your case is that it is being called from an API with no context. One possible way to make this work (although it's very hacky and I'm speculating here) would be to capture the synchronization context manually when you are registering the callback and making sure that you set it up/restore afterwards, but I would not go that way, at this point using events is more problematic than the value it brings, IMO. Nothing will save you now from having to write code for handling these errors, but you can potentially make it a oneliner if you write a couple of extension methods: public static async Task<T> HandleErrors<T>(this Task<T> invocation)
{
try
{
await invocation();
}
catch(Exception e)
{
...
}
} And then use it with
(Just an option/suggestion)
We are looking into ways we can make this better, allowing for errors to be dispatched to the renderer in thesescenarios, but that won't happen until our next release.
It's hard some time, but we need to balance giving people time to answer with our ability to efficiently triage the backlog, otherwise it rapidly becomes unmanageable. That said, when an issue is closed its not the end of it, if people come back in a few days we are happy to reopen the issue and continue the discussion if necessary 😃. |
@javiercn Thanks again for your fast reply 😊
Yes, that’s absolutely true and I’m working on fixing that ASAP 😄 Your clarification did help me though so thank you for the additional details! You are also absolutely righ that an exception from a background thread would crash the app. I guess my confusion comes from the fact that this background thread is also a thread that is currently handling a HTTP request and as such would be “protected” by the middleware pipeline. But with this all being async and over a synchronization context, I guess that this still makes it a normal background thread for the Blazor app and as such implies all the usual caveats.
I’ve already started on reworking my app to load data only within the lifecycle methods, so that should probably make everything work better without taking too long to write everything 😅 RE that extension method: My main problem would be exactly that error handling within that
If you are looking into this, then that’s already all I am asking for! Having a way to trigger this in the future would be great! And don’t worry about the issue management. I totally understand why it’s necessary, and it probably doesn’t even prevent these processes from still being very painful for you as maintainers. – You did see my reply and even replied back, so all is well! Again, thank you very much for your responses! It helped me a lot and I think I have a good idea now on how to prevent error from disappearing silently and/or crashing my app 😁 |
Up until now, I was under the impression that I could use
ComponentBase.InvokeAsync
with server-side Blazor to lift some asynchronous work back onto the renderer. I use this primarily in cases where I have a C# event, which is synchronous, and I want to then do something asynchronously to update the component. For example:This pattern has been working really well for me so far. I can load the data initially when the component is rendered, and when the underlying data is updated, the event causes the component to refresh its view.
Now, the
LoadItems()
is pretty safe and will rarely fail. In the rare case there is an exception, I am fine with the server-side Blazor application crashing. I have designed the error dialog for this purpose so users know that something critical happened and that they need to refresh the page to retry. This may not be the most graceful way to handle problems but it works pretty well in this case and does not require handling exceptions all over the place.However, I now realized that exceptions that are thrown within the
InvokeAsync
action are not actually bubbling up to the renderer.While the lifecycle methods are run directly through the renderer by calling
AddToPendingTasks
which will then handle errors inGetErrorHandledTask
, it appears that callInvokeAsync
will just run the action through the dispatcher which will eventually just return the task without doing any error handling.So I am basically in an async void scenario without noticing it where exceptions thrown within
InvokeAsync
disappear into the void. – Ouch.Is there any good approach on how to do global error handling for situations like this in server-side Blazor? I am not really looking forward to adding custom error handling to all my components. So if there was a way to trigger the default unhandled error experience, I would be very happy if I could just slap that around all
InvokeAsync
calls and keep the error behavior as I intended. Unfortunately,TryNotifyClientErrorAsync
is not accessible from the outside and the other methods likeDispatchEvent
do more than I would like to do here.The text was updated successfully, but these errors were encountered: