Задача.Запуск внутри backgroundservice неожиданно перестает работать

#c# #asp.net #.net #multithreading #task-parallel-library

#c# #asp.net #.net #многопоточность #задача-параллельная-библиотека

Вопрос:

Я использую Task.Run внутри backgroundservice. У меня есть backgroundservice, который отвечает за вход в систему и отправку сердцебиения для конкретной системы.

 public class TestBGService : IHostedService
{
    private readonly ITestService _testService;
    private bool MustLogOn { get; set; } = true;
    private string id { get; set; }
    public TestBGService(ITestService testService)
    {
        _testService = testService;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            Log.Information("TestBGService starts Initialize");
            await Initialize();
        }
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }

    private async Task Initialize()
    {
        try
        {
            if (MustLogOn)
            {
                Log.Information("TestBGService ExecuteAsync trying to LogOnAsync");
                id = await _testService.LogOnAsync();
                if (!string.IsNullOrEmpty(id))
                {
                    Log.Information($"new id equals to {id}");
                    MustLogOn = false;

                    _ = Task.Run(async () =>
                     {
                         while (true)
                         {
                             bool res = await SendHeartBeat(id);
                             Log.Information($"res from SendHeartBeat {res}");
                             if (!res)
                             {
                                 break;
                             }
                             await Task.Delay(10000);
                         }
                     });
                    await _testService.StartProcessAsync(id);
                }
            }
        }
        catch (Exception ex)
        {
            Log.Error($"TestBGService ExecuteAsync throws {ex.ToString()}");
        }
    }


    private async Task<bool> SendHeartBeat(string id)
    {
        bool isSuccess = true;
        try
        {
            Log.Information("TestBGService sending heartbeat at "   DateTime.Now);
            var response = new HeartBeatResponseModel();
            response = await _testService.SendHeartBeatAsync(id);
            Log.Information("TestBGService heartbeat response equals to "   response.IsSuccessful);
            if (!response.IsSuccessful)
            {
                MustLogOn = true;
                isSuccess = response.IsSuccessful;
            }
        }
        catch (Exception ex)
        {
            Log.Error(ex, "TestBGService SendHeartBeat throws");
            isSuccess = false;
            MustLogOn = true;
        }
        return isSuccess;
    }

}
  

Инициализируйте метод, пытающийся войти в систему, и в случае успеха он должен начать отправлять Heartbeat. Метод SendHeartBeat отвечает за получение успеха или сбоя. В случае успеха я изменяю значение MustLogon на false и отправляю SendHeartBeat каждые 10 секунд. Параллельно для SendHeartBeat мне нужно позвонить _testService.StartProcessAsync , чтобы получить данные из потока. Каким-то образом он перестает работать и снова начинает вход в систему, но мне нужно, чтобы до тех пор, пока он не вернет false, он должен работать, и SendHeartBeat необходимо выполнять каждые 10 секунд, но, к сожалению, он перестает работать в случае bool res = true и в этот момент он не выдает никаких исключений. Есть предложения?

Комментарии:

1. Взгляните на этот пример: learn.microsoft.com/en-us/aspnet/core/fundamentals/host / … и попробуйте соответствующим образом изменить свой код. Вы не должны вечно зависать в методе startAsync.

2. Например, он останавливается, когда я отправляю CancellationToken

3. Да, потому что это мусор (извините). Пожалуйста, посмотрите, как должна работать служба синхронизации (см. Ссылку в первом комментарии). В обратном вызове таймера: 1. Войдите в систему, если необходимо, 2. Отправьте Heartbeat. Выполнено. Не запускайте задачи изнутри задач…

4. Или вы можете просто быстро выполнить грязное использование BackgroundService по предоставленной ссылке @Fildor, переместив свою » Initialize » логику в ExecuteAsync (и удалив Task.Run , какой в этом смысл здесь?).

5. Буквально ничто не мешает вам сделать это.

Ответ №1:

Это то, что я придумал, перейдя по ссылке из базового класса BackgroundService и «подключив» необходимую функциональность: (непроверенный, конечно)

 public class TestBGService : BackGroundService
{
    // I guess you are using different logging
    private readonly ILogger<TestBGService> _logger; 
    // Your service to send Heartbeats to
    private readonly ITestService _testService;
    // If id is null, we need to login.
    private string _id = null;

    public TestBGService (ILogger<TestBGService> logger, ITestService testService)
    {
        _logger = logger;
        _testService = testService ?? throw new ArgumentNullException(nameof(testService));
    }

    public Task StartAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("TestBGService  running.");
        return Task.CompletedTask;
    }

    // This will be triggered by the runtime.
    private async Task ExecuteAsync (System.Threading.CancellationToken stoppingToken)
    {
        while( !stoppingToken.CancellationRequested )
        {
            try
            {
                if( string.IsNullOrEmpty(_id) )
                { 
                     // _id is null => perform login
                     _id = await _testService.LogOnAsync(); // perhaps pass stoppingToken?
                }
                bool res = await SendHeartBeat(id); // again: consider passing stoppingToken
                if( !res ) _id = null; // Heartbeat unsuccessful: login next time
                await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
            }
            catch(Exception ex) // You should actually catch more specific exceptions.
            {
                // TODO log Exception
                _id = null; // Reset id, so we login next time.
            }

        }
    }

    private async Task<bool> SendHeartBeat(string id)
    {
        // Don't even try if login was unsuccessful.
        if ( string.IsNullOrEmpty(id) ) return false;

        _logger.LogInformation("TestBGService sending heartbeat at {0}" , DateTime.Now);
        var response = await _testService.SendHeartBeatAsync(id);
        _logger.LogInformation("TestBGService heartbeat response {0}successful", response.IsSuccessful ? "" : "un");
        return response.IsSuccessful;
    }

    public Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("TestBGService  is stopping.");
        // TODO maybe explicit logout?
        return Task.CompletedTask;
    }
}