.NET Core应用程序在使用Topshelf和IApplicationHostLifetime.StopApplication()时挂起。

huangapple go评论68阅读模式
英文:

.NET Core application hanging when using Topshelf with IApplicationHostLifetime.StopApplication()

问题

我有一个运行在Windows服务中的.NET Core应用程序,使用Topshelf来管理。我想要实现的是,服务能够在某个触发条件下(在我的测试应用中,启动后几秒钟)停止自身。问题是,我的应用似乎在启动时出现了问题,而且我对目前找到的唯一解决方案并不满意。

这是我能够想到的最简单的代码示例。您可能会注意到稍微奇怪的CreateAndStartHost()方法,这个方法基本上是从我们实际的应用程序中复制/粘贴过来的,所以我们不能真正改变它。Run()扩展方法基本上只是Task.Factory.StartNew()的包装。

到目前为止唯一有效的方法是在ApplicationStopping.Register(() => { ... })中调用并且不等待_host.StopAsync(),然后在ApplicationStopped.Register(() => { ... })中设置一些标志,然后在TestService.Stop()中等待它。这种方法的问题在于,我仍然希望能够在TestService.Stop()中等待StopAsync(),而将其保存在一个Task中似乎有点太过巧妙。

// Program.cs - 创建并运行Topshelf主机
HostFactory.New(x =>
{
    x.Service<TestService>(sc =>
    {
        sc.ConstructUsing(() => new TestService());
        sc.WhenStarted((s, hostControl) => s.Start(hostControl));
        sc.WhenStopped((s, hostControl) => s.Stop(hostControl));
        sc.WhenShutdown((s, hostControl) =>
        {
            Log.Information("On TopShelf Shutdown");
            s.Stop(hostControl);
        });
    });
    x.SetDescription("TopshelfWithDotNetDi");
    x.SetDisplayName("TopshelfWithDotNetDi");
    x.SetServiceName("TopshelfWithDotNetDi");
    x.RunAsLocalSystem();
    x.StartAutomaticallyDelayed();
    x.EnableShutdown();
    x.UnhandledExceptionPolicy = UnhandledExceptionPolicyCode.LogErrorOnly;
    x.OnException(ex =>
    {
        Log.Information(ex.Message);
    });
}).Run();

// TestServices.cs
public class TestService : ServiceControl
{
    private IHost _host;

    public bool Start(HostControl hostControl)
    {
        Log.Information("Starting host...");

        _host = CreateAndStartHost().GetAwaiter().GetResult();

        var hostApplicationLifetime = _host.Services.GetService<IHostApplicationLifetime>();
        hostApplicationLifetime.ApplicationStopping.Register(() => { hostControl.Stop(); });
        hostApplicationLifetime.ApplicationStopped.Register(() => { Log.Information("Application stopped..."); });

        return true;
    }

    public bool Stop(HostControl hostControl)
    {
        Log.Information("Stopping host...");

        var stopTask = _host.StopAsync();
        while (!stopTask.Wait(1000)) // <-- 问题:这将永远循环
        {
            hostControl.RequestAdditionalTime(TimeSpan.FromSeconds(1));
        }

        return true;
    }

    private static Task<IHost> CreateAndStartHost()
    {
        // 无法真正更改此部分,抱歉
        var taskScheduler = new AsyncContextThread().Factory.Scheduler;
        return taskScheduler.Run(async () =>
        {
            var host = new HostBuilder()
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHostedService<MyHostedService>();
                }).Build();

            await host.StartAsync();
            return host;
        });
    }
}

// MyHostedService 只会在5秒循环后调用 _applicationLifetimeHost.StopApplication()
// 所以我不在示例中包含这部分

请注意,以上代码中的注释是我提供的翻译。

英文:

I have a .NET Core application that is running as a Windows service using Topshelf. What I want to do is for the service to be able to stop itself on some trigger (in my test app, a few seconds after startup). The problem is that my app seems to hang on startup and I'm not quite happy with the only solution I found so far.

This is the simplest code sample I could come up with. You might notice the slightly weird CreateAndStartHost(), that one is mostly copy/pasted from our actual app, so we can't really change that. The Run() extension method is basically just a wrapper over Task.Factory.StartNew().

The only thing that worked so far is to call and not wait _host.StopAsync() in ApplicationStopping.Register(() =&gt; { ... }) and then set some flag in ApplicationStopped.Register(() =&gt; { ... }) which will then be waited for in TestService.Stop(). The problem with this approach is that I would still like to be able to await StopAsync() inside TestService.Stop() and saving it in a Task feels a bit too hacky.

// Program.cs - Create and run Topshelf host
HostFactory.New(x =&gt;
{
    x.Service&lt;TestService&gt;(sc =&gt;
    {
        sc.ConstructUsing(() =&gt; new TestService());
        sc.WhenStarted((s, hostControl) =&gt; s.Start(hostControl));
        sc.WhenStopped((s, hostControl) =&gt; s.Stop(hostControl));
        sc.WhenShutdown((s, hostControl) =&gt;
        {
            Log.Information(&quot;On TopShelf Shutdown&quot;);
            s.Stop(hostControl);
        });
    });
    x.SetDescription(&quot;TopshelfWithDotNetDi&quot;);
    x.SetDisplayName(&quot;TopshelfWithDotNetDi&quot;);
    x.SetServiceName(&quot;TopshelfWithDotNetDi&quot;);
    x.RunAsLocalSystem();
    x.StartAutomaticallyDelayed();
    x.EnableShutdown();
    x.UnhandledExceptionPolicy = UnhandledExceptionPolicyCode.LogErrorOnly;
    x.OnException(ex =&gt;
    {
        Log.Information(ex.Message);
    });
}).Run();

// TestServices.cs
public class TestService : ServiceControl
{
    private IHost _host;

    public bool Start(HostControl hostControl)
    {
        Log.Information(&quot;Starting host...&quot;);

        _host = CreateAndStartHost().GetAwaiter().GetResult();

        var hostApplicationLifetime = _host.Services.GetService&lt;IHostApplicationLifetime&gt;();
        hostApplicationLifetime.ApplicationStopping.Register(() =&gt; { hostControl.Stop(); });
        hostApplicationLifetime.ApplicationStopped.Register(() =&gt; { Log.Information(&quot;Application stopped...&quot;); });

        return true;
    }

    public bool Stop(HostControl hostControl)
    {
        Log.Information(&quot;Stopping host...&quot;);

        var stopTask = _host.StopAsync();
        while (!stopTask.Wait(1000)) // &lt;-- PROBLEM: This will loop forever
        {
            hostControl.RequestAdditionalTime(TimeSpan.FromSeconds(1));
        }

        return true;
    }

    private static Task&lt;IHost&gt; CreateAndStartHost()
    {
        // Can&#39;t really change this bit, sorry
        var taskScheduler = new AsyncContextThread().Factory.Scheduler;
        return taskScheduler.Run(async () =&gt;
        {
            var host = new HostBuilder()
                .ConfigureServices((hostContext, services) =&gt;
                {
                    services.AddHostedService&lt;MyHostedService&gt;();
                }).Build();

            await host.StartAsync();
            return host;
        });
    }
}

// MyHostedService will just call _applicationLifetimeHost.StopApplication()
// after a 5s loop, so I&#39;m not including this bit in the sample

答案1

得分: 1

这可能不是你寻找的答案,但我不会使用Topshelf。它似乎是一个被放弃的项目(最近的提交是三年前)。

你不需要Topshelf来能够在Windows服务中运行.NET,因为这在本地得到支持:

文档链接

英文:

This might not be the answer you're looking for, but I wouldn't use Topshelf. It seems like an abandoned project (latest commit three years ago).

You don't need Topshelf to be able to run .NET in a Windows service as this is supported natively:

Documentation

huangapple
  • 本文由 发表于 2023年6月27日 17:35:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/76563510.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定