reactiveui / ReactiveUI

An advanced, composable, functional reactive model-view-viewmodel framework for all .NET platforms that is inspired by functional reactive programming. ReactiveUI allows you to abstract mutable state away from your user interfaces, express the idea around a feature in one readable place and improve the testability of your application.
https://www.reactiveui.net
MIT License
7.99k stars 1.12k forks source link

[Bug]: ReactiveCommand causing application to hang with FilePicker in Avalonia #3833

Closed EoinMorgan17 closed 1 month ago

EoinMorgan17 commented 1 month ago

Describe the bug 🐞

Found an issue while trying to bind a ReactiveCommand<Unit, string> to a method that opens a FilePicker in Avalonia. When using the ReactiveCommand to bind a button to the Avalonia FilePicker and return the resulting string, the application hangs indefinitely. This occurs when using the ReactiveCommand to call a standard Avalonia implementation of a FilePicker for opening a file https://docs.avaloniaui.net/docs/basics/user-interface/file-dialogs

This action is demonstrated in the linked reproduction repository, where the issue can be reproduced using the button labelled "Select File using ReactiveCommand". Debugging, it can be seen that the Select File Dialog opens, however when the user selects a file and clicks Open, the filePicker never returns, it enters a thread loop and hangs indefintely.

This was noted when using a ReactiveCommand as both the variable type and assigned type for the command, for example, the property and constructor looks like this:

public ReactiveCommand<Unit, string> ReactiveOpenFileCommand { get; }

public StartWindowViewModel()
{
    ReactiveOpenFileCommand = ReactiveCommand.Create(() => this.ShowFileDialogAsyncWithResult().Result);
}

However, if the variable type used is an ICommand and it is then assigned a ReactiveCommand, the issue is not observed, for example, with a property and constructor such as:

public ICommand ICommandOpenFileCommand { get; }

public StartWindowViewModel()
{
    ICommandOpenFileCommand = ReactiveCommand.Create(() => this.ShowFileDialogAsyncVoid());
}

With the property set to an ICommand, the FilePicker returns as expected and functions fine. This behaviour is demonstrated in the repo linked below, where there are two buttons, one for each of the solutions above, and it can be clearly seen that one functions fine and the other hangs.

The current workaround we are using is to use the ICommand, although the command cannot then be subscribed to or return a result, so we are setting a string property and subscribing to the value change, however this is not an ideal solution.

If you could look into this issue it would be much appreciated, I tried to debug the threading issue but it went beyond my knowledge unfortunately. If there are any other details I can provide to assist with reproduction or debugging please let me know.

Thank you!

Step to reproduce

  1. Create a ReactiveCommand<Unit, string> and bind it to a method that calls an Avalonia FilePicker to open a file using OpenFilePickerAsync (the method will be an async Task to return the result.
  2. Bind the command to a button.
  3. Click on the button and select a file in the File dialog, then click Open.
  4. The application will hang indefinitely and will not return the file string.

Reproduction repository

https://github.com/EoinMorgan17/ReactiveCommandIssueDemo

Expected behavior

FilePicker should return a file object that can then be used to set the string return value of the ReactiveCommand

Screenshots 🖼️

No response

IDE

Visual Studio 2022

Operating system

Windows

Version

No response

Device

No response

ReactiveUI Version

20.1.1

Additional information ℹ️

No response

anaisbetts commented 1 month ago

Your hang is related to your use of Result here which causes a blocking call, but because this is a UI operation, you deadlock - instead, you want to just use ShowFileDialogAsyncWithResult()

EoinMorgan17 commented 1 month ago

Thanks @anaisbetts , I didn't realise that would cause a block on the thread. Switching to just using the method call and using ReactiveCommand.CreateFromTask() rather than ReactiveCommand.Create() has resolved this for us. Thanks for your help!

github-actions[bot] commented 3 weeks ago

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.