Skip to content
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

Please add an asynchronous Initialize function to the Application #17610

Closed
RRQM opened this issue Nov 25, 2024 · 9 comments
Closed

Please add an asynchronous Initialize function to the Application #17610

RRQM opened this issue Nov 25, 2024 · 9 comments

Comments

@RRQM
Copy link

RRQM commented Nov 25, 2024

Is your feature request related to a problem? Please describe.

The current Application class only has the Initialize synchronization function.

If I have some business that requires network communication and need to register communication components in the IOC container, then there will be problems.

Firstly, the connection of communication components is asynchronous, for example: Task ConnectAsync()

And container registration must be synchronized, so I had to initialize the components in advance and register them as singleton.

For example:

public override async void Initialize()
{
    var services = new ServiceCollection();
    var client = new WebSocketDmtpClient();
    await client.SetupAsync(new TouchSocketConfig()
         .SetDmtpOption(new DmtpOption { VerifyToken = "Dmtp" })
         .ConfigurePlugins(a => a.UseDmtpRpc())
         .SetRemoteIPHost("ws://localhost:5043/WebSocketDmtp"));
    await client.ConnectAsync();

    services.AddSingleton<IWebSocketDmtpClient>(client);

    _serviceProvider = services.BuildServiceProvider();
    AvaloniaXamlLoader.Load(this);
}

But obviously, the use of async void here is very bad.

Describe the solution you'd like

There is an asynchronous support Initialize method, for example: Task InitializeAsync ()

Describe alternatives you've considered

I feel almost irreplaceable

Additional context

No response

@timunie
Copy link
Contributor

timunie commented Nov 25, 2024

I think the async operations shouldn't be fone before the ui starts otherwise the user may get confused due to long loading times.

@RRQM
Copy link
Author

RRQM commented Nov 26, 2024

Thanks for your reply.

But I think adding a new async initialization might be a better solution. As for your suggestion that there shouldn't be a time-consuming effect when the program loads, I think this should be the choice of other developers, not the limitation of the framework itself.

Taking a step back, a lot of the time, the internet connection is also very fast and doesn't affect the loading of the program.

But since his connection is asynchronous, I should finish the asynchronous build when initializing. can be injected into the container, but it's clear that this isn't possible with the current synchronous approach.

Actually, if it's in a desktop program, I can just use task.GetAwaiter().GetResult(); to wait for the async to complete.

But in the web, this method will not be allowed to be used.

So, I think it's essential for the current CSharp ecosystem to load programs, or other rewritable functions, and provide asynchronous related methods.

Blazor, for example, provides a lot of synchronous and asynchronous APIs.

Finally, I looked at the relevant source code and found that it was actually not very difficult to add asynchronous initialization.

The most important thing is that it doesn't break the existing API. It's just a new addition.

@stevemonaco
Copy link
Contributor

stevemonaco commented Nov 26, 2024

The problem with having Initialize or OnFrameworkInitializationCompleted become async is people will use them for long and/or unreliable operations. For file I/O involving small config files, sync I/O is still usable because the UI thread lacks pressing concerns because there's no visual yet.

What you can do is isolate you app into two parts: a very tiny part without DI and the real app with DI. The part without DI will be shown first and show a splash window (or view if mobile). This will show while loading and you will also construct your IoC container from this part. Once loaded, you then you navigate to your main app's view using the IoC just built and everything is the same as before. A third-party library could be made for this, but it might be too opinionated to include within Avalonia.

There's likely a smart way to do this without an isolated splash screen, but your main window will have similar restrictions until DI kicks in to allow population/creation of child views. There's probably some work here to develop/teach better patterns.

@RRQM
Copy link
Author

RRQM commented Nov 26, 2024

I see what you mean, but I still don't think it should be limited by frameworks.

In short, if you support asynchronous methods, you won't have any problems performing synchronization, but if you call asynchronous methods in synchronous methods, unpredictable problems will occur.

Please rethink the KISS principle.

@maxkatz6
Copy link
Member

"Async void" is bad primarily for two reasons:

  • Error handling can be dangerous.
  • You can't await this method.

Error handling can be dangerous.

I am going to move second one out of the discussion quickly. In XAML frameworks task continuation goes to Dispatcher synchronization context by default. Which also means unhandled exceptions will go to Dispatcher thread and can be handled there. It won't crash any secondary threads but will go directly to the main thread. In case of app initialization, result is practically the same. The same goes to event handlers, which often are "async void" too.
Although, I still wouldn't recommend relying on "async void" when you can avoid it.

You can't await this method.

While it's true, Avalonia initialization code cannot be fully asynchronous:

  1. Async Main must be sync. Otherwise macOS won't be supported.
  2. Sync over async via GetAwaiter().GetResult() will cause deadlocks, as app needs UI sync.context inside.
  3. We could use DispatcherFrame for safer sync over async magic (the similar way, as WPF ShowDialog is sync without blocking UI operations). But we can't support DispatcherFrame on mobile and browser.

Meaning, we only can make App.Initialize() async Task without actually waiting for it... Which exactly how it is now.

@maxkatz6
Copy link
Member

This code has another issue though:

    await client.ConnectAsync();
// ...
    AvaloniaXamlLoader.Load(this);

This code might cause issues with the fact that App.xaml content was ignored at a time AppBuilder completed initialization. And only was completed at some point later. All styles and app properties initialization will be delayed.
I would rather recommend using OnFrameworkInitializationCompleted as an async void entrypoint.

@maxkatz6
Copy link
Member

Blazor, for example, provides a lot of synchronous and asynchronous APIs.

With Browser in general it's a special story.
Browser C# apps don't have a "true" Main method. It's just a method, which is executed at some point after web page processed main JS script, and browser doesn't wait for this initialization to be completed before rendering anything.

@thevortexcloud
Copy link
Contributor

thevortexcloud commented Nov 26, 2024

Can't you just rewrite this to use a factory method? EG

public class WebSocketDmtpClientFactory {
    private static IWebSocketDmtpClient Instance { get; set; }

    public async Task<IWebSocketDmtpClient> GetClientAsync() {
            if (Instance is not null} {
                return instance;
            }
    
            var client = new WebSocketDmtpClient();
            
             await client.SetupAsync(new TouchSocketConfig()
             .SetDmtpOption(new DmtpOption { VerifyToken = "Dmtp" })
             .ConfigurePlugins(a => a.UseDmtpRpc())
             .SetRemoteIPHost("ws://localhost:5043/WebSocketDmtp"));
        
             Instance = client;
             return client
        }
}

....

services.AddSingleton<WebSocketDmtpClientFactory>();

Obviously you still can't do async injection with this though.

There is also a discussion here about adding real async support for MSDI:

dotnet/runtime#65656

@RRQM
Copy link
Author

RRQM commented Nov 27, 2024

Thank you for your replies. I have a general understanding of the reasons why Initialize cannot be asynchronous and the current temporary solution.

Thanks again

@RRQM RRQM closed this as completed Nov 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants