Недавно мы (CMS.Core 12.16.0) внесли изменения для лучшей поддержки асинхронных потоков выполнения async/await, а также изоляции разрешения службы от контейнера IOC в параллельных контекстах.
Это изменение может повлиять на код, который порождает несколько параллельных задач. В таком коде могут возникнуть ошибки типа «Невозможно создать команду без существующего соединения» или «Соединение закрыто». Причина этого в том, что для поддержки асинхронного выполнения async/await контекст базы данных «объединяется» вместе с другим контекстом выполнения. Но что касается потоков async/await, контекст выполнения также сохраняется, например, Задача.Выполнить или Task.Factory.StartNew звонки. Это приводит к тому, что если новые задачи создаются параллельно, несколько одновременных задач будут использовать один и тот же контекст базы данных, что может вызвать проблемы, поскольку контекст базы данных не является потокобезопасным. Это может, например, привести к исключениям, упомянутым ранее.
ФонКонтекст
Для поддержки сценариев, в которых новые задачи/потоки создаются параллельно, мы ввели сервис IBackgroundContextFactory (он был представлен в CMS.Core.12.16.0, но есть ошибка, которая была устранена в CMS.Core.12.17.1, поэтому рекомендуется использовать эту версию или более позднюю). Он имеет один метод, например:
///
/// Used to create a new context that has its own scope for services and request caches.
/// Recommended to use for background task/threads
///
public interface IBackgroundContextFactory
{
///
/// Creates a new context that has its own scope for services and request caches.
///
/// A disposable context.
IBackgroundContext Create();
}
Созданный контекст будет содержать изолированный контекст для выполнения, что означает, что он будет иметь собственную область действия. IServiceProvider (который будет удален при удалении контекста) и изолированный контекст выполнения (включая контекст базы данных). Выполнение в этом фоновом контексте также поддерживает асинхронное выполнение async/await.
Поставщик услуг с заданной областью также обеспечит сохранение услуг до тех пор, пока контекст. Это защищает от таких вещей, как ObjectDisposeException, которые вы могли получить до этого, если создали параллельную задачу в HTTP-запросе (например, обработчик событий, например, IContentEvents, который запускает фоновую задачу). Причина ObjectDisposeException заключается в том, что задача будет использовать контейнер с заданной областью из http-запроса, а затем, если http-запрос завершится до фоновой задачи, вы можете получить ObjectDisposeException.
Рекомендуемое использование
Если у вас есть код, который порождает новые задачи/потоки (например, Задача.Выполнить, Task.Factory.StartNew, Параллельный.ForEach или неожиданные задачи), то если код, который будет выполняться в задаче, обращается к другим сервисам, то рекомендуется создать фоновый контекст для потока. Итак, скажем, например, у вас есть такой код:
var tasks = new List();
foreach (var thing in things)
{
tasks.Add(Task.Run(() =>
{
//Some code that run in parallel
}));
}
Task.WaitAll(tasks.ToArray());
Тогда было бы предложено изменить его на:
var tasks = new List();
foreach (var thing in things)
{
tasks.Add(Task.Run(() =>
{
using (var backgroundContext = _backgroundContextFactory.Create())
//below is example on how to get a scoped service
//ServiceLocator.Current and Injected<> also works with background context (even if usage of those are not recommended as best practice)
var isolatedScopedService = backgroundContext.Service.Get();
//Some code that run in parallel
}));
}
Task.WaitAll(tasks.ToArray());
Поэтому рекомендуется, чтобы первый оператор внутри задачи/потока создавал фоновый контекст для выполнения задачи.
Примечание что вам не нужно (и не следует) создавать новый фоновый контекст в «обычном» потоке выполнения async/await (то есть, когда вы ожидаете задач, а не выполняете их параллельно). Потому что в этом случае одновременно выполняется только один поток, и в этом случае вы хотите, чтобы контекст выполнения (включая контекст базы данных) перемещался между потенциально разными потоками, которые являются частью асинхронного потока выполнения async/await.
31 августа 2023 г.