Open vsfeedback opened 2 years ago
This is required e.g. to save content to disk when application closes and restore state from disk when application starts in any MAUI Blazor application. Glad to see it fixed for future release!
Is there any suggested way to detect that the Dispose() is called because the application closes in Blazor components and pages? (Dispose is called as well when the page is leaved)
We've decided to hold off on addressing this directly for now.
It's not currently possible to determine with complete certainty when a MAUI control's ViewHandler
can be safely disposed, and this extends to the BlazorWebView
control. We're still investigating the best techniques for managing handler life cycle (see #7381). We may revisit this topic in a future date if we are confident there is a way to manage ViewHandler
disposal without breaking the less conventional uses of BlazorWebView
. For more information on the reasoning behind this decision, refer to this thread.
To fix the specific case reported in this issue, consider subscribing to the Window.Destoying
event that disposes resources that absolutely must be cleaned up before the application closes. You can register a singleton service to share state between MAUI and Blazor contexts and track which objects are awaiting disposal.
Persisting state by saving data to disk, on the other hand, should ideally be done before the application is on its way out. Saving to disk periodically (or when there is a notable change to the application's state) would be a more reliable approach because the application is not guaranteed to close gracefully (process gets terminmated, devices loses power, etc.).
This caused me a lot of grief recently. Here's a hack that worked for my unique circumstance:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
public BlazorWebView WebView
{
get => _webView;
}
}
public partial class App : Application
{
public App()
{
InitializeComponent();
var mainPage = new MainPage();
_webView = mainPage.WebView;
MainPage = mainPage;
}
readonly BlazorWebView _webView;
protected override Window CreateWindow(IActivationState? activationState)
{
var window = base.CreateWindow(activationState);
window.Destroying += Window_Destroying;
return window;
}
private void Window_Destroying(object? sender, EventArgs e)
{
_webView.Handler?.DisconnectHandler();
}
}
We've decided to hold off on addressing this directly for now.
It's not currently possible to determine with complete certainty when a MAUI control's
ViewHandler
can be safely disposed, and this extends to theBlazorWebView
control. We're still investigating the best techniques for managing handler life cycle (see #7381). We may revisit this topic in a future date if we are confident there is a way to manageViewHandler
disposal without breaking the less conventional uses ofBlazorWebView
. For more information on the reasoning behind this decision, refer to this thread.To fix the specific case reported in this issue, consider subscribing to the
Window.Destoying
event that disposes resources that absolutely must be cleaned up before the application closes. You can register a singleton service to share state between MAUI and Blazor contexts and track which objects are awaiting disposal.Persisting state by saving data to disk, on the other hand, should ideally be done before the application is on its way out. Saving to disk periodically (or when there is a notable change to the application's state) would be a more reliable approach because the application is not guaranteed to close gracefully (process gets terminmated, devices loses power, etc.).
I understand those reasons very well being one of those who tried to create video player with full-screen mode in Xamarin Forms back then. However, persisting handlers is very advanced scenario and if developer wants it - he knows what he's doing. What currently happens is framework not working as expected - I'm not too sure average engineer should care if some sort of Handler is released - it is responsibility of the framework. I'm wondering if it makes sense to allow developer to opt if he/she wants to persist specific handler via property/method override: default value will disconnect WebView's handler as soon as hosting component is destroying - it will make sure 95% of scenarios will be covered and resources (being entire Blazor web app!!!! in case of Blazor Desktop) are released. For those who wants to persist handler will just override default value and tinker with component/handler as needed.
@MackinnonBuck hi pleased what is the state of this issue? It is important for MAUI Blazor
@janseris does a solution like mentioned in this earlier comment work for you?
@janseris does a solution like mentioned in this earlier comment work for you?
I don't know. I haven't tried. It is a workaround.
Here's a hack...
I am waiting for a fix.
I think the hack is in fact not a hack at all. It's just disposing a disposable thing when the app shuts down.
I think the hack is in fact not a hack at all. It's just disposing a disposable thing when the app shuts down.
Actually, it's disconnecting a handler, and that indirectly causes the object to be disposed. But, the fundamental problem is that is not the responsibility of the user to implement. Whatever connected the handler and created the object assumes the responsibility to disconnect the handler and dispose the object.
OK I do admit it's a bit hacky 😁 But it should be completely safe to do in this case when the app is shutting down, because disposing is idempotent, meaning it is acceptable to dispose more than one time.
I agree this should be done entirely automatically by the system, but that is not a viable option right now.
I have encountered the same issue, I'm trying to remove an event when the secondary window is closed using the dispose event since there is an event which still fires off in the background which is causing NullReferenceException errors.
The fix mentioned above also doesn't work for me since after _webView.Handler?.DisconnectHandler();
is called, the dispose method is called, but then a NullReferenceException error is raised from Microsoft.AspNetCore.Components.WebView.Maui.dll
I was able to fix a similar issue with the following code (just as a workaround):
BlazorWebViewHandler.BlazorWebViewMapper.AppendToMapping("Dispose", (handler, view) =>
{
if (view is View v)
{
v.Unloaded += (s, e) =>
{
var disconnectMethod = handler.GetType().GetMethod("DisconnectHandler", BindingFlags.NonPublic | BindingFlags.Instance);
disconnectMethod?.Invoke(handler, new[] { handler.PlatformView });
};
}
});
In my case we navigated between pages within the application and we had no issue when closing the application.
Verified this issue with Visual Studio Enterprise 17.9.0 Preview 2. Can repro this issue.
We have a great experience with Blazor-Hybrid approach; even with Multi-Window. But there is a catch that brought me here:
If a maui window gets destroyed, we are trying to trigger disposing of the BlazorWebView and all its components.
Combining hints from @PureWeen to use Unloaded
and @MackinnonBuck information to mark end of life via blazorWebView.Handler?.DisconnectHandler();
we ran into "Window was already deactivated" exception (maybe related to #22406 ).
Our final workaround to prevent memory leak now relies on a manual call to the GC when the BlazorWebView calls back to Unloaded, like so:
// MainPage.xaml.cs (one and only page per window that contains the BlazorWebView)
private void BlazorWebView_Unloaded(object sender, EventArgs e)
{
// Trying to trigger disposing of the blazor webview....
BlazorWebView.RootComponents.Clear();
BlazorWebView = null;
// platform specific (basically casting the BlazorWebView for example to Microsoft.UI.Xaml.Controls.WebView2 and calling Close() on it as suggested by @Eilon
WindowService.UnloadWebView(blazorWebView);
// !!! leads to exception "Window was already deactivated"
//BlazorWebView.Handler?.DisconnectHandler();
// Since we are not able to find a proper way to trigger the disposing....
var timeStamp = Stopwatch.GetTimestamp();
GC.Collect();
GC.WaitForPendingFinalizers(); // TODO ? is not necessary to prevent the leak
var elapsed = Stopwatch.GetElapsedTime(timeStamp);
Log.Debug("BlazorWebView unloaded. GC collected in {elapsed} ms", elapsed.Milliseconds);
}
This basically removes the memory leak.
Maybe this approach helps, or someone can improve it?
Since we use Multiple windows for our Application, we can't migrate from wpf to .maui, please fix, else we can't dispose properly since window and blazor do not share the same servicescope
This issue has been moved from a ticket on Developer Community.
[severity:I'm unable to use this version] I am having serious component-lifetime issue. The problem is with implementing Dispose from IDisposable on a razor component. The method is perfectly called one the page gets off from viewing, ex. when switching to other view. Also, the OnInitialized() is being called. But, once clicking the [x] close of the entire application, the Dispose() does not happen. As a result null-reference exceptions appear whenever the background tasks not being stop before application close try to access not existing objects. It is not much seen in example below, but it happens in a larger application when a timer is hit during application close.
How to reproduce: 1) Create a new application from template 2) Add a System.Threading.Timer object to SurveyPrompt.razor and init it in OnInitialized() 3) add @implements IDisposable 4) Add Dispose() => timer?.Dispose(); 5) Run the application 6) The Dispose() / OnInitialized() works perfectly when switching between Home/Counter menu items, but when closing application with top-right [X] the Dispose() is not being hit.
IMHO this issue prevents MAUI to go public, I cannot control background tasks and cannot dispose resources properly.
Original Comments
Feedback Bot on 5/16/2022, 01:47 AM:
(private comment, text removed)
Original Solutions
(no solutions)