Библиотека параллельных задач и длительные задачи

У меня есть BackgroundService (IHostedService), который реализует ExecuteAsync аналогично примеру в Реализуйте фоновые задачи в микросервисах с помощью IHostedService и класса BackgroundService..

Я хочу запустить несколько задач, которые одновременно выполняют длительную команду во внешней системе. Я хочу, чтобы служба продолжала выполнять эти задачи до тех пор, пока служба не будет остановлена, но я не хочу одновременно выполнять команду с теми же параметрами (если она выполняется с item.parameter1 = 123, я хочу, чтобы она подождала до 123 готово, затем снова выполните 123). Кроме того, он не должен блокировать и не должен пропускать память. Если возникает исключение, я хотел бы корректно остановить проблемную задачу, зарегистрировать ее и перезапустить. Каждое выполнение команды получает разные параметры, примерно так:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    var items = GetItems(); //GetItems returns a List<Item>

    _logger.LogDebug($"ExternalCommandService is starting.");

    stoppingToken.Register(() => 
            _logger.LogDebug($" Execute external command background task is stopping."));

    while (!stoppingToken.IsCancellationRequested)
    {
        _logger.LogDebug($"External command task is doing background work.");

        //Execute the command with values from the item
        foreach(var item in items)
        {
             ExecuteExternalCommand(item);
        }

        await Task.Delay(_settings.CheckUpdateTime, stoppingToken);
    }

    _logger.LogDebug($"Execute external command background task is stopping.");

}

Структура данных довольно проста:

public class MyData
{
    public string Foo { get; set; }
    public string Blee { get; set; }
}

Когда дело доходит до разработки задач, я новичок, поэтому, пожалуйста, простите меня за непонимание. Было бы разумнее сделать ExecuteExternalCommand асинхронным? Я не уверен, что эти задачи выполняются параллельно. Что я делаю неправильно? Как выполнить другие требования, такие как обработка исключений и плавный перезапуск задач? Пожалуйста помоги.

Что есть в QueryTheDatabase?

Peter Bons 10.11.2018 21:29

Это синхронный метод, который вызывает длительную команду в другой системе. Имя, которое я дал ему здесь, не совсем точно отражает то, что он делает, я обновлю его.

TrevorBrooks 10.11.2018 22:30

Ваш код выполняется последовательно, и асинхронность не означает параллельного выполнения. Если вы хотите запустить цикл foreach параллельно, используйте Parallel.ForEach(items, ExecuteExternalCommand);. Кроме того, вы действительно хотите, чтобы ваши элементы обрабатывались снова и снова в вашем практически бесконечном цикле while? Не следует ли когда-нибудь очищать список элементов?

ckuri 11.11.2018 11:36

Что касается других ваших требований, вместо прямого вызова ExecuteExternalCommand выполните Parallel.ForEach(items, item => { while (!stoppingToken.IsCancellationRequested) try { ExecuteExternalCommand(item); break; } catch (Exception e) { _logger.LogError(e); } });, чтобы при возникновении исключения оно регистрировалось и команда повторялась. Если команда выполнена успешно, код выйдет из цикла. Или вы можете просто удалить элемент из своего списка, когда он был успешно выполнен, если их нужно выполнить только один раз.

ckuri 11.11.2018 11:53

По мере их завершения я бы хотел, чтобы они выполнялись снова и снова, пока они не будут отменены или служба не будет остановлена.

TrevorBrooks 12.11.2018 14:50

если ExecuteExternalCommand - это длительная команда, возможно, стоит передать ей токен отмены, чтобы ее можно было отменить, иначе ваша служба не остановится, пока не вернется

ESG 12.11.2018 19:36

Сколько операций вы хотите выполнить? Похоже, вам нужна уникальная операция для каждого уникального типа. item.parameter1 = 123 об операции, которая зацикливается до остановки и так далее. В таком случае рекомендации для Parallel могут быть не лучшим вариантом. Это для разделения одной сложной операции на множество (разветвление и соединение). Я считаю, что ваша работа немного проще, если вы можете использовать запрос, чтобы разбить параметры на группы или один список и отправить каждый из них в собственный метод Task, который может работать до тех пор, пока не будет остановлен.

Michael Puckett II 12.11.2018 21:00

@Michael Puckett II Да, для каждого элемента в элементах я хочу, чтобы задача выполняла свою работу, возвращалась по завершении и затем запускалась снова. Я бы хотел, чтобы каждая задача выполнялась одновременно с разными элементами.

TrevorBrooks 12.11.2018 21:10

@TrevorBrooks, если у вас много разных задач с разными требованиями к расписанию, вам, вероятно, стоит обратить внимание на подходящий планировщик заданий, такой как Hangfire. ExecuteAsync - это просто метод, вызываемый для выполнения всего, что вы хотите, в фоновом режиме.

Panagiotis Kanavos 13.11.2018 10:32
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
9
715
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Примечание. Этот пример может оказаться бесполезным из-за того, как имитируются данные. Если вы укажете правильную структуру данных в своем вопросе, я обновлю ответ, чтобы он соответствовал.

У вас есть данные.

public struct Data
{
    public Data(int value) => Value = value;
    public int Value { get; }
    public string Text => $"{nameof(Data)}: {Value}";
}

Вы хотите, чтобы данные были сгруппированы по определенному значению. (ПРИМЕЧАНИЕ: этот пример может быть нелогичным, потому что он группирует данные по Value, который уже является уникальным. Это добавлено только для примера :)

Есть много способов сделать это, но я буду использовать Linq, Distinct и IEqualityComparer<T> для этого примера, который сравнивает Data.Value.

public class DataDistinction : IEqualityComparer<Data>
{
    public bool Equals(Data x, Data y) => x.Value == y.Value;
    public int GetHashCode(Data obj) => obj.Value.GetHashCode();
}

Издеваться над данными В этом примере я имитирую некоторые данные.

var dataItems = new List<Data>();

for (var i = 0; i < 10; i++)
{
    dataItems.Add(new Data(i));
}

Обработка данных В этом примере я буду использовать IEqualityComparer<T>, чтобы получить каждый Data уникальным образом с помощью Value и начать обработку.

private static void ProcessData(List<Data> dataItems)
{
    var groupedDataItems = dataItems.Distinct(new DataDistinction());

    foreach (var data in groupedDataItems)
    {
        LoopData(data); //We start unique loops here.
    }
}

Зациклить уникальные данные

В этом примере я решил запустить новый Task, используя Task.Factory. Это будет одноразовое использование, но обычно оно вам не нужно. В этом примере я передаю Action<object>, где объект представляет состояние или параметр, переданный Task. Я также разбираю state, который вы увидите, и запускаю цикл while. Цикл while контролирует CancellationTokenSource на предмет отмены. Для удобства я объявил CancellationTokenSource статически в приложении; который вы увидите в полной версии консольного приложения внизу.

private static void LoopData(Data data)
{
    Task.Factory.StartNew((state) =>
    {
        var taskData = (Data)state;
        while (!cancellationTokenSource.IsCancellationRequested)
        {
            Console.WriteLine($"Value: {taskData.Value}\tText: {taskData.Text}");
            Task.Delay(100).Wait();
        }
    },
    data,
    cancellationTokenSource.Token,
    TaskCreationOptions.LongRunning,
    TaskScheduler.Default);
}

Теперь я поместил все это в одно консольное приложение, которое вы можете копировать, вставлять и исследовать.

Это возьмет ваши данные, разделит их и запустит все во всем Task на неопределенный срок, пока программа не будет завершена.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Question_Answer_Console_App
{
    class Program
    {
        private static readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
        static void Main(string[] args)
        {
            var dataItems = new List<Data>();

            for (var i = 0; i < 10; i++)
            {
                dataItems.Add(new Data(i));
            }   

            ProcessData(dataItems);
            Console.ReadKey();
            cancellationTokenSource.Cancel();
            Console.WriteLine("CANCELLING...");
            Console.ReadKey();
        }

        private static void ProcessData(List<Data> dataItems)
        {
            var groupedDataItems = dataItems.Distinct(new DataDistinction());

            foreach (var data in groupedDataItems)
            {
                LoopData(data);
            }
        }

        private static void LoopData(Data data)
        {
            Task.Factory.StartNew((state) =>
            {
                var taskData = (Data)state;
                while (!cancellationTokenSource.IsCancellationRequested)
                {
                    Console.WriteLine($"Value: {taskData.Value}\tText: {taskData.Text}");
                    Task.Delay(100).Wait();
                }
            },
            data,
            cancellationTokenSource.Token,
            TaskCreationOptions.LongRunning,
            TaskScheduler.Default);
        }

        ~Program() => cancellationTokenSource?.Dispose();
    }

    public struct Data
    {
        public Data(int value) => Value = value;
        public int Value { get; }
        public string Text => $"{nameof(Data)}: {Value}";
    }

    public class DataDistinction : IEqualityComparer<Data>
    {
        public bool Equals(Data x, Data y) => x.Value == y.Value;
        public int GetHashCode(Data obj) => obj.Value.GetHashCode();
    }
}
//OUTPUT UNTIL CANCELLED
//Value: 0        Text: Data: 0
//Value: 3        Text: Data: 3
//Value: 1        Text: Data: 1
//Value: 2        Text: Data: 2
//Value: 4        Text: Data: 4
//Value: 5        Text: Data: 5
//Value: 6        Text: Data: 6
//Value: 7        Text: Data: 7
//Value: 8        Text: Data: 8
//Value: 9        Text: Data: 9
//Value: 5        Text: Data: 5
//Value: 1        Text: Data: 1
//Value: 7        Text: Data: 7
//Value: 4        Text: Data: 4
//Value: 0        Text: Data: 0
//Value: 8        Text: Data: 8
//Value: 9        Text: Data: 9
//Value: 2        Text: Data: 2
//Value: 3        Text: Data: 3
//Value: 6        Text: Data: 6
//Value: 5        Text: Data: 5
//Value: 3        Text: Data: 3
//Value: 8        Text: Data: 8
//Value: 4        Text: Data: 4
//Value: 1        Text: Data: 1
//Value: 2        Text: Data: 2
//Value: 9        Text: Data: 9
//Value: 7        Text: Data: 7
//Value: 6        Text: Data: 6
//Value: 0        Text: Data: 0
//Value: 8        Text: Data: 8
//Value: 5        Text: Data: 5
//Value: 3        Text: Data: 3
//Value: 2        Text: Data: 2
//Value: 1        Text: Data: 1
//Value: 4        Text: Data: 4
//Value: 9        Text: Data: 9
//Value: 7        Text: Data: 7
//Value: 0        Text: Data: 0
//Value: 6        Text: Data: 6
//Value: 2        Text: Data: 2
//Value: 3        Text: Data: 3
//Value: 5        Text: Data: 5
//Value: 8        Text: Data: 8
//Value: 7        Text: Data: 7
//Value: 9        Text: Data: 9
//Value: 1        Text: Data: 1
//Value: 4        Text: Data: 4
//Value: 6        Text: Data: 6
//Value: 0        Text: Data: 0
//Value: 2        Text: Data: 2
//Value: 3        Text: Data: 3
//Value: 5        Text: Data: 5
//Value: 7        Text: Data: 7
//Value: 8        Text: Data: 8
//Value: 9        Text: Data: 9
//Value: 4        Text: Data: 4
//Value: 1        Text: Data: 1
//Value: 0        Text: Data: 0
//Value: 6        Text: Data: 6
//Value: 3        Text: Data: 3
//Value: 2        Text: Data: 2
//Value: 1        Text: Data: 1
//Value: 9        Text: Data: 9
//Value: 5        Text: Data: 5
//Value: 8        Text: Data: 8
//Value: 7        Text: Data: 7
//Value: 4        Text: Data: 4
//Value: 6        Text: Data: 6
//Value: 0        Text: Data: 0
//Value: 2        Text: Data: 2
//Value: 3        Text: Data: 3
//Value: 4        Text: Data: 4
//Value: 9        Text: Data: 9
//Value: 1        Text: Data: 1
//Value: 7        Text: Data: 7
//Value: 8        Text: Data: 8
//Value: 5        Text: Data: 5
//Value: 0        Text: Data: 0
//Value: 6        Text: Data: 6
//Value: 4        Text: Data: 4
//Value: 3        Text: Data: 3
//Value: 2        Text: Data: 2
//Value: 5        Text: Data: 5
//Value: 7        Text: Data: 7
//Value: 9        Text: Data: 9
//Value: 8        Text: Data: 8
//Value: 1        Text: Data: 1
//Value: 6        Text: Data: 6
//Value: 0        Text: Data: 0
//Value: 2        Text: Data: 2
//Value: 4        Text: Data: 4
//Value: 3        Text: Data: 3
//Value: 5        Text: Data: 5
//Value: 8        Text: Data: 8
//Value: 9        Text: Data: 9
//Value: 7        Text: Data: 7
//Value: 1        Text: Data: 1
//Value: 0        Text: Data: 0
//Value: 6        Text: Data: 6
//Value: 2        Text: Data: 2
//Value: 4        Text: Data: 4
//Value: 3        Text: Data: 3
//Value: 1        Text: Data: 1
//Value: 7        Text: Data: 7
//Value: 5        Text: Data: 5
//Value: 8        Text: Data: 8
//Value: 9        Text: Data: 9
//Value: 6        Text: Data: 6
//Value: 0        Text: Data: 0
//Value: 2        Text: Data: 2
// CANCELLING...

В комментариях вы спрашивали, как писать в консоль, когда Task готов. Вы можете await и Task в этом примере, и когда Task будет готов, вы можете распечатать этот Data для демонстрации. async void не рекомендуется по уважительной причине, но это единственный случай, когда он используется правильно.

Вот обновленный LoopData с подписью async await.

private static async void LoopData(Data data)
{
    await Task.Factory.StartNew((state) =>
    {
        var taskData = (Data)state;
        while (!cancellationTokenSource.IsCancellationRequested)
        {
            Console.WriteLine($"Value: {taskData.Value}\tText: {taskData.Text}");
            Task.Delay(100).Wait();
        }
    },
    data,
    cancellationTokenSource.Token,
    TaskCreationOptions.LongRunning,
    TaskScheduler.Default);

    Console.WriteLine($"Task Complete: {data.Value} : {data.Text}");
}

//OUTPUT
//Value: 0        Text: Data: 0
//Value: 1        Text: Data: 1
//Value: 3        Text: Data: 3
//Value: 2        Text: Data: 2
//Value: 4        Text: Data: 4
//Value: 5        Text: Data: 5
//Value: 6        Text: Data: 6
//Value: 7        Text: Data: 7
//Value: 8        Text: Data: 8
//Value: 9        Text: Data: 9
//Value: 0        Text: Data: 0
//Value: 2        Text: Data: 2
//Value: 3        Text: Data: 3
//Value: 1        Text: Data: 1
//Value: 5        Text: Data: 5
//Value: 4        Text: Data: 4
//Value: 7        Text: Data: 7
//Value: 9        Text: Data: 9
//Value: 8        Text: Data: 8
//Value: 6        Text: Data: 6
//Value: 0        Text: Data: 0
//Value: 3        Text: Data: 3
//Value: 2        Text: Data: 2
//Value: 4        Text: Data: 4
//Value: 5        Text: Data: 5
//Value: 1        Text: Data: 1
//Value: 6        Text: Data: 6
//Value: 9        Text: Data: 9
//Value: 8        Text: Data: 8
//Value: 7        Text: Data: 7
//Value: 0        Text: Data: 0
//Value: 3        Text: Data: 3
//Value: 2        Text: Data: 2
//Value: 1        Text: Data: 1
//Value: 4        Text: Data: 4
//Value: 5        Text: Data: 5
//Value: 9        Text: Data: 9
//Value: 6        Text: Data: 6
//Value: 7        Text: Data: 7
//Value: 8        Text: Data: 8
//Value: 0        Text: Data: 0
//Value: 2        Text: Data: 2
//Value: 3        Text: Data: 3
//Value: 1        Text: Data: 1
//Value: 5        Text: Data: 5
//Value: 4        Text: Data: 4
//Value: 8        Text: Data: 8
//Value: 7        Text: Data: 7
//Value: 9        Text: Data: 9
//Value: 6        Text: Data: 6
//Value: 0        Text: Data: 0
//Value: 3        Text: Data: 3
//Value: 2        Text: Data: 2
//Value: 4        Text: Data: 4
//Value: 1        Text: Data: 1
//Value: 5        Text: Data: 5
//Value: 8        Text: Data: 8
//Value: 9        Text: Data: 9
//Value: 6        Text: Data: 6
//Value: 7        Text: Data: 7
//Value: 0        Text: Data: 0
//Value: 2        Text: Data: 2
//Value: 3        Text: Data: 3
//Value: 5        Text: Data: 5
//Value: 4        Text: Data: 4
//Value: 1        Text: Data: 1
//Value: 8        Text: Data: 8
//Value: 7        Text: Data: 7
//Value: 6        Text: Data: 6
//Value: 9        Text: Data: 9
// CANCELLING...
//Task Complete: 0 : Data: 0
//Task Complete: 2 : Data: 2
//Task Complete: 3 : Data: 3
//Task Complete: 1 : Data: 1
//Task Complete: 5 : Data: 5
//Task Complete: 4 : Data: 4
//Task Complete: 8 : Data: 8
//Task Complete: 6 : Data: 6
//Task Complete: 7 : Data: 7
//Task Complete: 9 : Data: 9..

Это здорово, спасибо ... просто работаю над этим.

TrevorBrooks 12.11.2018 22:21

Я добавил структуру своих данных.

TrevorBrooks 13.11.2018 00:09

Спасибо за ответ, это мне очень помогло. Я смог включить это в свою работу. Последний вопрос, если это не слишком сложно, как мне писать в консоль, когда каждая задача завершена?

TrevorBrooks 14.11.2018 22:28

Привет, @TrevorBrooks, я обновил вопрос вашим комментарием здесь, внизу. Дайте мне знать, если у вас возникнут дополнительные вопросы и извинения. Я не видел предыдущих до сих пор.

Michael Puckett II 15.11.2018 00:30

Спасибо. Эта линия фактически никогда не вызывается. : - /

TrevorBrooks 15.11.2018 14:56

Это произойдет, когда вы нажмете клавишу на консоли. Задача настроена на бесконечное выполнение, пока вы не прикажете ей остановиться. Этот пример разработан таким образом, что вы нажимаете любую клавишу, чтобы остановить задачи, а затем снова нажимаете любую клавишу, чтобы закрыть приложение.

Michael Puckett II 15.11.2018 17:33

Другие вопросы по теме