dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
21.84k stars 1.67k forks source link

WinRT.Runtime exception on async file writing #22936

Open touchofevil-dev opened 4 weeks ago

touchofevil-dev commented 4 weeks ago

Description

I have a windows MAUI app. As part of this app, I update contents of a file. I am using MVVM pattern with CommunityToolkit.MVVM. I have below code snippets.

SettingsVM (Inherits from ObservableObject)

[RelayCommand]
private async Task SaveSettingsAsync(CancellationToken token)
{
    ErrorMessage = string.Empty;
    try
    {
        Settings settings = new() { SettingA = "Test", SettingB = "AnotherSetting" };
        await _settingsManager.WriteSettingsAsync(Constants.SettingsFilePath, settings, token).ConfigureAwait(false);
        await Shell.Current.GoToAsync("..").ConfigureAwait(false);
    }
    catch (Exception ex)
    {
        // exception caught here.
    }
}

SettingsManager

public async Task WriteSettingsAsync(string filepath, Settings settings, CancellationToken cancellationToken = default)
{
    string? dirName = Path.GetDirectoryName(filepath);
    if (!Directory.Exists(dirName))
    {
        Directory.CreateDirectory(dirName!);
    }
    // serialize settings object to a string
    string serializedSettings = JsonSerializer.Serialize(settings);

    await File.WriteAllTextAsync(filepath, serializedSettings, cancellationToken).ConfigureAwait(false);
}

This seems to throw below exception.

at WinRT.ExceptionHelpers.gThrow|39_0(Int32 hr) at ABI.Microsoft.UI.Xaml.Controls.ICommandBarMethods.get_PrimaryCommands(IObjectReference _obj) at Microsoft.UI.Xaml.Controls.CommandBar.get_PrimaryCommands() at Microsoft.Maui.Controls.Toolbar.UpdateMenu() at Microsoft.Maui.Controls.Toolbar.MapToolbarItems(IToolbarHandler arg1, Toolbar arg2) at Microsoft.Maui.PropertyMapperExtensions.<>cDisplayClass2_02.<ReplaceMapping>b__0(TViewHandler h, TVirtualView v, Action2 p) at Microsoft.Maui.PropertyMapperExtensions.<>cDisplayClass1_02.<ModifyMapping>g__newMethod|0(IElementHandler handler, IElement view) at Microsoft.Maui.PropertyMapper2.<>c__DisplayClass5_0.b0(IElementHandler h, IElement v) at Microsoft.Maui.PropertyMapper.UpdatePropertyCore(String key, IElementHandler viewHandler, IElement virtualView) at Microsoft.Maui.PropertyMapper.UpdateProperty(IElementHandler viewHandler, IElement virtualView, String property) at Microsoft.Maui.Handlers.ElementHandler.UpdateValue(String property) at Microsoft.Maui.Controls.Toolbar.SetProperty[T](T& backingStore, T value, String propertyName) at Microsoft.Maui.Controls.Toolbar.set_ToolbarItems(IEnumerable`1 value) at Microsoft.Maui.Controls.ShellToolbar.ApplyChanges() at Microsoft.Maui.Controls.ShellToolbar.<.ctor>b110(Object , ShellNavigatedEventArgs ) at Microsoft.Maui.Controls.Shell.SendNavigated(ShellNavigatedEventArgs args) at Microsoft.Maui.Controls.Shell.<.ctor>b1550(Object , ShellNavigatedEventArgs args) at Microsoft.Maui.Controls.ShellNavigationManager.<>cDisplayClass16_0.gFireNavigatedEvents|3(ShellNavigatedEventArgs a, Shell shell) at Microsoft.Maui.Controls.ShellNavigationManager.<>cDisplayClass16_0.b__2() at Microsoft.Maui.Controls.BaseShellItem.OnAppearing(Action action) at Microsoft.Maui.Controls.ShellNavigationManager.HandleNavigated(ShellNavigatedEventArgs args) at Microsoft.Maui.Controls.ShellNavigationManager.GoToAsync(ShellNavigationParameters shellNavigationParameters, ShellNavigationRequest navigationRequest) at MyTestApp.ViewModels.SettingsVM.SaveSettingsAsync(CancellationToken token)

However, when I replace my code of SettingsManager to its sync version, I do not get that exception.

SettingsManager

public Task WriteSettingsAsync(string filepath, Settings settings, CancellationToken cancellationToken = default)
{
    string? dirName = Path.GetDirectoryName(filepath);
    if (!Directory.Exists(dirName))
    {
        Directory.CreateDirectory(dirName!);
    }
    // serialize settings object to a string
    string serializedSettings = JsonSerializer.Serialize(settings);

    File.WriteAllText(filepath, serializedSettings);
    return Task.CompletedTask;
}

I had this app in dotnet 7 and upgraded this to dotnet 8, and I am able to reproduce this behavior on both the versions. While the exception is being thrown on navigation, it somehow seems to be linked to file writing, since if I comment out the actual file writing or if I use the sync version it doesn't seem to throw any exception.

System Details: Dotnet version - 8.0.301 Visual Studio - 17.10.1 MAUI workload - maui-windows 8.0.21/8.0.100 SDK 8.0.300, VS 17.10.34928.147 Target framework - net8.0-windows10.0.19041.0

Packages: (on .net 8) Microsoft.Maui.Controls - 8.0.40 Microsoft.Maui.Controls.Compatibility - 8.0.40 CommunityToolkit.Maui - 9.0.1 CommunityToolkit.Mvvm - 8.2.2

(on .net 7) - Pre upgrade CommunityToolkit.Maui - 5.1.0 CommunityToolkit.Mvvm - 8.2.0

Steps to Reproduce

  1. Run the application on Windows (Visual Studio).
  2. Click on the "Settings" button.
  3. On Settings page, Click on "Save" button.

UI will navigate back to main page however, you'll notice that you'd get an exception.

Link to public reproduction project repository

FileIssue.zip

Version with bug

8.0.3 GA

Is this a regression from previous behavior?

Not sure, did not test other versions

Last version that worked well

Unknown/Other

Affected platforms

Windows

Affected platform versions

net8.0-windows10.0.19041.0, net7.0-windows10.0.19041.0

Did you find any workaround?

Workaround was to call the sync version of the File.WriteAllText method and return Task.CompletedTask. Other methods like using

using (var fileStream = File.OpenWrite(filepath))
{
    await JsonSerializer.SerializeAsync(fileStream, settings, cancellationToken: cancellationToken).ConfigureAwait(false);
}

seem to have worked too, though I haven't thoroughly tested this implementation as it was causing another issue and I found this issue while replacing this existing implementation.

Relevant log output

at WinRT.ExceptionHelpers.<ThrowExceptionForHR>g__Throw|39_0(Int32 hr)
   at ABI.Microsoft.UI.Xaml.Controls.ICommandBarMethods.get_PrimaryCommands(IObjectReference _obj)
   at Microsoft.UI.Xaml.Controls.CommandBar.get_PrimaryCommands()
   at Microsoft.Maui.Controls.Toolbar.UpdateMenu()
   at Microsoft.Maui.Controls.Toolbar.MapToolbarItems(IToolbarHandler arg1, Toolbar arg2)
   at Microsoft.Maui.PropertyMapperExtensions.<>c__DisplayClass2_0`2.<ReplaceMapping>b__0(TViewHandler h, TVirtualView v, Action`2 p)
   at Microsoft.Maui.PropertyMapperExtensions.<>c__DisplayClass1_0`2.<ModifyMapping>g__newMethod|0(IElementHandler handler, IElement view)
   at Microsoft.Maui.PropertyMapper`2.<>c__DisplayClass5_0.<Add>b__0(IElementHandler h, IElement v)
   at Microsoft.Maui.PropertyMapper.UpdatePropertyCore(String key, IElementHandler viewHandler, IElement virtualView)
   at Microsoft.Maui.PropertyMapper.UpdateProperty(IElementHandler viewHandler, IElement virtualView, String property)
   at Microsoft.Maui.Handlers.ElementHandler.UpdateValue(String property)
   at Microsoft.Maui.Controls.Toolbar.SetProperty[T](T& backingStore, T value, String propertyName)
   at Microsoft.Maui.Controls.Toolbar.set_ToolbarItems(IEnumerable`1 value)
   at Microsoft.Maui.Controls.ShellToolbar.ApplyChanges()
   at Microsoft.Maui.Controls.ShellToolbar.<.ctor>b__11_0(Object _, ShellNavigatedEventArgs __)
   at Microsoft.Maui.Controls.Shell.SendNavigated(ShellNavigatedEventArgs args)
   at Microsoft.Maui.Controls.Shell.<.ctor>b__155_0(Object _, ShellNavigatedEventArgs args)
   at Microsoft.Maui.Controls.ShellNavigationManager.<>c__DisplayClass16_0.<HandleNavigated>g__FireNavigatedEvents|3(ShellNavigatedEventArgs a, Shell shell)
   at Microsoft.Maui.Controls.ShellNavigationManager.<>c__DisplayClass16_0.<HandleNavigated>b__2()
   at Microsoft.Maui.Controls.BaseShellItem.OnAppearing(Action action)
   at Microsoft.Maui.Controls.ShellNavigationManager.HandleNavigated(ShellNavigatedEventArgs args)
   at Microsoft.Maui.Controls.ShellNavigationManager.GoToAsync(ShellNavigationParameters shellNavigationParameters, ShellNavigationRequest navigationRequest)
   at MyTestApp.ViewModels.SettingsVM.SaveSettingsAsync(CancellationToken token)
github-actions[bot] commented 4 weeks ago

Hi I'm an AI powered bot that finds similar issues based off the issue title.

Please view the issues below to see if they solve your problem, and if the issue describes your problem please consider closing this one and thumbs upping the other issue to help us prioritize it. Thank you!

Open similar issues:

Closed similar issues:

Note: You can give me feedback by thumbs upping or thumbs downing this comment.

drasticactions commented 4 weeks ago

Could you create a small reproduction sample with how you've implemented this?

Based on what you've written, I'm not sure if this is a MAUI UI issue yet.

This feels like a threading issue, but I'm not sure if this is related to a bug in MAUI, something in your code, something with WinUI, or something else.

touchofevil-dev commented 4 weeks ago

Could you create a small reproduction sample with how you've implemented this?

Yes I have added the same in the issue. Updated reproduction steps too.

Based on what you've written, I'm not sure if this is a MAUI UI issue yet.

  • If you use File.WriteAllTextAsync and don't navigate with the shell, does that work?

  • I'm unsure which thread that relay command is run on. If you run the Shell.Current.GoTo command on the UI Dispatcher thread, does that work?

This feels like a threading issue, but I'm not sure if this is related to a bug in MAUI, something in your code, something with WinUI, or something else.

Yes even I wasn't sure, however the only time I faced this issue was when navigating. All other calls work well, so I have filed it here as a starting point. Another weird bit was I am only facing it on that particular screen (my assumption being due to file I/O operation only being on that screen). Navigation from other screens without file I/O doesn't seem to throw any exception.

Just to be clear, the navigation itself works from UI perspective, so on UI you'd actually see the screen you navigated to, however in the background you'd also get this exception.

drasticactions commented 4 weeks ago

@PureWeen What do you think?

Zhanglirong-Winnie commented 3 weeks ago

Verified this issue with Visual Studio 17.11.0 Preview 2.0 & 17.10.1 (8.0.40&8.0.21&8.0.3). Not repro on Windows platform with sample project.

touchofevil-dev commented 3 weeks ago

Verified the same on another machine to rule out for machine specific issues. Able to reproduce using provided project. Also tried using VS 17.10.2 which is a released version. Note: I have added empty catch block in the sample project so you'll need to put debugger breakpoint on catch block of SettingsVM.cs file.

Attaching screenshot: Screenshot 2024-06-12 174558