I'm writing a WinUI3 (Project Reunion 0.5) application with .NET 5 and would like to use the .NET Generic Host. I'm using the default host with a custom IHostedService
:
public App() {
_host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
services.AddHostedService<MyHostedService>();
}).Build();
InitializeComponent();
}
The hosted service performs some asynchronous operations in StopAsync
. For demonstration purposes, let's say it delays for 1 second (this code still produces the issue):
public override async Task StopAsync(CancellationToken cancellationToken)
{
await Task.Delay(1000);
}
I start the host in OnLaunched
:
protected override async void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
await _host.StartAsync();
m_window = new MainWindow();
m_window.Activate();
}
I let the default ConsoleLifetime
implementation stop the host before the process exits.
The Task
returned by my IHostedService.StopAsync
implementation completes, but IHost.StopAsync
never returns and the process hangs with this message in the output:
Microsoft.Hosting.Lifetime: Information: Application is shutting down...
Microsoft.Hosting.Lifetime: Information: Waiting for the host to be disposed. Ensure all 'IHost' instances are wrapped in 'using' blocks.
If I step through with the debugger, sometimes the IHost.StopAsync
method will time out and an exception will be thrown. This never happens outside of the debugger. I have tried explicitly stopping and disposing the host when the MainWindow
is closed, but it didn't make any difference.
I thought perhaps the DispatcherQueueSynchronizationContext
was being shut down before the host could stop and tasks were not being serviced, but the DispatcherQueue.ShutdownStarting
event is never fired.
Any other ideas?
I took @Dai's advice from the comments and investigated running WinUI on a separate thread and running the host on the main thread.
I created an IHostedService
to manage the WinUI application:
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.System;
using Microsoft.UI.Xaml;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MyApp.Hosting
{
public class WinUIHostedService<TApplication> : IHostedService, IDisposable
where TApplication : Application, new()
{
private readonly IHostApplicationLifetime HostApplicationLifetime;
private readonly IServiceProvider ServiceProvider;
public WinUIHostedService(
IHostApplicationLifetime hostApplicationLifetime,
IServiceProvider serviceProvider)
{
HostApplicationLifetime = hostApplicationLifetime;
ServiceProvider = serviceProvider;
}
public void Dispose()
{
}
public Task StartAsync(CancellationToken cancellationToken)
{
var thread = new Thread(Main);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
private void Main()
{
WinRT.ComWrappersSupport.InitializeComWrappers();
Application.Start((p) => {
var context = new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(context);
new TApplication();
});
HostApplicationLifetime.StopApplication();
}
}
}
I defined DISABLE_XAML_GENERATED_MAIN
in the build settings and added my own Main
:
public class Program
{
public static void Main(string[] args)
{
Host.CreateDefaultBuilder()
.ConfigureServices(services =>
{
services.AddHostedService<WinUIHostedService<App>>();
})
.Build().Run();
}
}
Voila! The WinUI application still runs fine and the host stops cleanly when the main window closes, even when IHostedService.StopAsync
runs asynchronous code.
Note that this code is just the first thing that worked. It could probably be improved and I don't fully understand the Generic Host lifetime semantics.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With