AvaloniaUI / Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology
https://avaloniaui.net
MIT License
26.06k stars 2.25k forks source link

Please add an asynchronous Initialize function to the Application #17610

Open RRQM opened 1 day ago

RRQM commented 1 day ago

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 commented 18 hours ago

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 commented 15 hours ago

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 commented 15 hours ago

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 commented 14 hours ago

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 commented 13 hours ago

"Async void" is bad primarily for two reasons:

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 commented 13 hours ago

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 commented 13 hours ago

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 commented 7 hours ago

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:

https://github.com/dotnet/runtime/issues/65656