首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >.net核心,测试服务应用程序。离开worker循环后,直到控制台窗口关闭,程序才会停止。这是正常行为吗?

.net核心,测试服务应用程序。离开worker循环后,直到控制台窗口关闭,程序才会停止。这是正常行为吗?
EN

Stack Overflow用户
提问于 2022-05-12 10:45:06
回答 1查看 148关注 0票数 0

我正在测试一个.net核心6多线程服务工作者应用程序,它在sql server上处理成批的数据行和表。

我只使用serilog记录文件。

当我在调试模式下运行它时,会打开一个控制台窗口,但它是空的,仍然是空的。我使用计数器进行测试,这样在循环了这么多循环之后,主工作器中的ExecuteAsync()函数就会退出。

我注意到主工作人员返回后,我的program.cs main方法中的最后一个块没有中断(我有一个断点),程序在关闭控制台之前不会“返回”到visual,甚至没有控制台消息要求我按键。即使关闭控制台,最后{Log.CloseAndFlush}块也不会中断。之后,我发现我的调试输出打印了许多类似于

“线程0x7590已退出代码0 (0x0)。

线程0xdcc使用代码0 (0x0)退出。

线程0x6c68已退出代码0 (0x0)。

线程0x690c与代码0 (0x0)一起退出。

线程0x3ec0与代码0 (0x0)一起退出。

线程0x7f08已退出代码0(0x0)。“

关闭控制台窗口后,我得到以下输出

“程序'18244 pulse.deletemember.service.exe:程序跟踪‘与代码0 (0x0)一起退出。

程序“18244 pulse.deletemember.service.exe”的代码为3221225786 (0xc000013a)。

我已经包括了主要代码。有人能告诉我为什么会发生这种事吗?在类似的帖子中,有人认为这可能是由未处理的东西造成的。我通常使用BackgroundWorker,因为我完全了解他们的行为和处理他们等,这是等待任务的东西对我来说是新的。

program.cs

代码语言:javascript
复制
using Serilog;

namespace pulse.deletemember.service;

public class Program
{
  // With help from https://www.youtube.com/watch?v=_iryZxv8Rxw&t=1055s
  public static void Main(string[] args)
  {
    try
    {
      Environment.CurrentDirectory = AppDomain.CurrentDomain.BaseDirectory;


      // Create temp logger before dependency injection takes over
      Log.Logger = new LoggerConfiguration()
          .ReadFrom.Configuration(MyConfiguration.ConfigurationSettings)
          .CreateLogger();

      Log.Logger.Information("Application starting up");

      // Start the application and wait for it to complete
      CreateHostBuilder(args).Build().Run();
    }
    catch (Exception err)
    {
      Log.Fatal(err, "The application failed to start correctly.");
    }
    finally
    {
      Log.CloseAndFlush();
    }
  }

  /// <summary>
  /// This sets up app
  /// </summary>
  public static IHostBuilder CreateHostBuilder(string[] args)
  {
    return Host.CreateDefaultBuilder(args)
        .UseWindowsService()
        .UseSerilog()
        .ConfigureServices(ConfigureDelegate)
      ;
  }

  /// <summary>
  /// Example of Dependency Injection
  /// Injecting singleton instance of MyConfiguration.
  /// Tested that config is re-read if changed
  /// </summary>
  /// <param name="hostContext"></param>
  /// <param name="services"></param>
  private static void ConfigureDelegate(HostBuilderContext hostContext, IServiceCollection services)
  {
    // Injects Worker thread. This adds a singleton that lasts length of the application
    services.AddHostedService<Worker>();
    services.AddSingleton<IMyConfiguration,MyConfiguration>();
    services.AddSingleton<IRepository, Repository>();
    services.AddSingleton<IMemoryQueryClass, MemoryQueryClass>();
  }
}

worker.cs

代码语言:javascript
复制
using System.Data.SqlClient;

namespace pulse.deletemember.service
{
  internal class Worker : BackgroundService
  {
    private readonly ILogger<Worker> _logger;
    private readonly IMyConfiguration _configuration;
    private readonly IRepository _repository;
    private readonly IMemoryQueryClass _memoryQueryClass;
    private readonly List<Task> _taskQueue = new();
    private long _counter;

    public Worker(ILogger<Worker> logger, IMyConfiguration configuration, IRepository repository,
      IMemoryQueryClass memoryQueryClass)
    {
      _logger = logger;
      _configuration = configuration;
      _repository = repository;
      _memoryQueryClass = memoryQueryClass;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
      // Initialise the delay from config file
      var delay = _configuration.PollDelaySecs;

      _logger.LogInformation("Worker running at: {time} ({e} utc)", DateTime.UtcNow.ToString("g"),
        Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT"));

      while (!stoppingToken.IsCancellationRequested)
      {
        // are we finished yet? 
        if (_configuration.MaxCounter >= 0 && _counter >= _configuration.MaxCounter) break;

        _counter += _configuration.BatchSize;
        try
        {
          var memoryUsed = _memoryQueryClass.PrivateMemorySize;
          _logger.LogInformation("Loop starting at {time} utc. Private memory = {memoryUsed:N1} Mb",
            DateTime.UtcNow.ToString("g"),memoryUsed);

          await Task.Delay(delay * 1000, stoppingToken);
          /* https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/asynchronous-programming */
          _repository.ResetMutexes();

          stoppingToken.ThrowIfCancellationRequested();

          // Delete old records in membership deletions log table
          _repository.TruncateDeletionsLogTable();

          // Pull in a bunch of member rows for deletion
          using var members = _repository.ReadBatch();

          if (members.Count == 0)
          {
            // zero records read, so set delay to 'normal' interval and start loop again
            delay = _configuration.PollDelaySecs;
            continue;
          }

          // Set delay to zero while more than zero records read from deletions table
          delay = 0;
          var taskQueueQuery =
            from memberRow in members
            select _repository.MainDeleteProcessAsync(memberRow, stoppingToken);

          // Convert query to enumerable list
          _taskQueue.AddRange(taskQueueQuery.ToList());

          // While any task is available in the list, keep waiting.
          // Once queue is empty then start overall loop again
          while (_taskQueue.Any())
          {
            var finishedTask = await Task.WhenAny(_taskQueue);
            // remove task from queue so taskQueue knows when to exit loop
            _taskQueue.Remove(finishedTask);
            stoppingToken.ThrowIfCancellationRequested();
          }
        }
        catch (SqlException err)
        {
          // As errors are handled in their own threads, this handler will only
          // catch when reading in batch.
          _logger.LogError(err, "Sql Exception in worker");
          if (err.Number == 18487 || err.Number == 18488)
            _logger.LogError("err.Number == 18487 || err.Number == 18488, password expired or needs to be reset");

          // Set delay for a min to allow for reboots or whatever
          delay = 60;
        }
        catch (OperationCanceledException err)
        {
          _logger.LogError(err, "Stoppingtoken operation was cancelled");
          break;
        }
        catch (Exception err)
        {
          _logger.LogError(err, "General error caught in worker loop. Ignoring");
        }

      } // while

      if (stoppingToken.IsCancellationRequested)
        _logger.LogInformation("stoppingToken.Cancelled set. Exiting application");

      _logger.LogInformation("Service exited. Counter = {counter}",_counter);

    }// function
  }
}
EN

回答 1

Stack Overflow用户

发布于 2022-05-13 07:03:01

我试着用谷歌的几个查询找到答案,幸运的是我找到了答案。

首先,我发现服务的行为不像应用程序,即使在工作线程退出之后,它们也会继续运行。Tbh,为什么这是默认行为是一个谜,会有兴趣知道为什么。

How do I (gracefully) shut down a worker service from within itself?

在那里,我想知道如何创建一个可以在我的应用程序中使用的IHostApplicationLifetime实例。就在那时,我发现了一个关于上述链接的后续问题。它的实例已经被支持,只需要添加到工作线程的构造函数参数中。

How to get and inject the IHostApplicationLifetime in my service to the container (Console App)

我尝试添加_hostApplicationLifetime.StopApplication();作为worker循环中的最后一行,它成功了。在program.cs恢复中,我在控制台窗口中收到一个通知,日志文件被刷新和关闭。

但我仍然想了解为什么应用程序继续运行,尽管主循环退出,如果有人可以解释请解释。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72214184

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档