Используйте Task.Run с синхронным методом внутри цикла foreach - PullRequest
0 голосов
/ 29 апреля 2019

Я пытаюсь найти наилучший из возможных подходов к синхронному запуску метода C # параллельно. Для этого я выбрал Task.Run() внутри цикла foreach. т.е. добавьте все синхронные методы к задачам и используйте Task.WhenAll(tasks).Wait() для параллельного выполнения всех задач.

Извините, если я использую Task без его фактического назначения. Я делаю это, потому что DBContext НЕ является потокобезопасным, и у меня есть Mandatory, чтобы использовать один DBContext для каждой записи транзакции / БД. Поэтому я подумал, что делать мой метод async бесполезно, так как все задачи, которые я выполняю внутри метода, зависят друг от друга. Итак, я подумал, что лучше выполнить список тех же задач одновременно.

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

Пожалуйста, помогите мне подсказать, в порядке ли моя реализация в долгосрочной перспективе.

Код

    public void MainMethod()
    {
        foreach (var x in _ListUser)
            tasks.Add(Task.Run(() => Update1Record(x)));
        Task.WhenAll(tasks).Wait();
    }
    public string Update1Record(UserViewModel objUser)
    {
        using (var VibrantDbContext = new VIBRANT())
        using (var AuditDb = new VibrantAuditEntities())
        using (var VibrantTransaction = VibrantDbContext.Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted))
        using (var AuditTransaction = AuditDb.Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted))
        {
            try
            {
                VibrantDbContext.Database.Initialize(force: false);
                AuditDb.Database.Initialize(force: false);
                VibrantDbContext.Configuration.AutoDetectChangesEnabled = false;
                var _ObjUserItem = FillupDateTimeValues(objUser);
                ImportToDB(_ObjUserItem, 0, VibrantDbContext, AuditDb);
                BuildImportLog(objUser, VibrantDbContext, AuditDb);
                VibrantDbContext.SaveChanges();
                AuditDb.SaveChanges();
                VibrantTransaction.Commit();
                AuditTransaction.Commit();
            }
            catch (Exception ex)
            {
                VibrantTransaction.Rollback();
                AuditTransaction.Rollback();
                throw;
            }
        }
        return "S";
    }

Ответы [ 2 ]

0 голосов
/ 29 апреля 2019

Пара моментов, связанных с дизайном вашего кода, что происходит, когда:

foreach (var x in _ListUser)
            tasks.Add(Task.Run(() => Update1Record(x)));
        Task.WhenAll(tasks).Wait();
  • _ListUser имеет 1000+ записей, и вы создаете 1000+ Task, и это поставит вашу систему на колени, независимо от конфигурации сервера
  • Хотя задача не отображается напрямую на поток, но для метода длительного выполнения требуется объем огромного количества запрашиваемых потоков, гораздо больше, чем могут эффективно обрабатывать ядра системы, что приводит к огромному переключению контекста и значительному снижению производительности

Какие есть варианты:

  • Parallel.Foreach относительно лучше, поскольку он не позволяет выделять слишком много потоков, но все же не является хорошей идеей для обработки дБ / вызовов ввода-вывода.
  • Лучшим вариантом было бы использовать Async - Await, но проблема в том, что ваш код заключается в том, что вы создаете обертку «Задача» по методу синхронизации, который не поможет Async Await, поскольку все же для него потребуются фоновые потоки на внутреннем сервере и таким образом, похожая проблема, только пользовательский интерфейс или вызывающий поток останутся отзывчивыми, но в случае Async - Await нам нужно использовать подлинные Async API, чтобы получить максимальную выгоду.

Ниже приведены рекомендуемые модификации:

  • Сделайте метод public string Update1Record асинхронным, таким образом, вместо этого вернув Task<string>, я предполагаю, что DBContext предоставляет API-интерфейсы Async
  • Измените MainMethod следующим образом:

    public async Task MainMethod()
    {
        foreach (var x in _ListUser)
        {
            var localx = x;
            tasks.Add(Update1Record(localx));
        }
        await Task.WhenAll(tasks);
    }
    

    Обратите внимание на localx для захвата локальной переменной, чтобы избежать проблемы закрытия.

Кроме того, почему Async лучше для таких сценариев:

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

  • Идея состоит в том, чтобы не блокировать и не резервировать драгоценные ресурсы, такие как Thread, которые ограничены для каждого процесса. Диспетчеризация и получение результата - это небольшая часть общей обработки, поток блоков приводит к потере важных вычислительных ресурсов

0 голосов
/ 29 апреля 2019

Существует ПАРАЛЛЕЛЬНЫЙ foreach, который вы можете использовать в этом случае. Вы можете увидеть этот пост для получения дополнительной информации:

Параллельная документация foreach

...