Как добавить обратный вызов при создании нового потока в ASP.NET Core?

У меня есть HTTP-сервер ASP.NET Core, работающий в .NET 5. Библиотеку, которую я использую, необходимо инициализировать один раз для каждого потока. В идеале я мог бы добавить какой-нибудь обратный вызов, чтобы вызывать код инициализации, когда веб-сервер ASP.NET запускает поток. Существует ли такая вещь?

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

Обновление: похоже, что ASP.NET использует пул потоков .NET по умолчанию. Пока не знаю, что делать с этой информацией, но если есть способ запустить этот обратный вызов для всех потоков в пуле потоков, это сработает для меня.

Почему? Каждый запрос обслуживается отдельным потоком из пула потоков. Если вы используете внутрипроцессное развертывание IIS, пул предоставляется IIS. Изменение потока в пуле (например, путем изменения его CultureInfo) может вызвать непредвиденные проблемы при следующем использовании.

Panagiotis Kanavos 18.12.2020 09:47

Вы используете тег F#, поэтому неудивительно, что неизменяемость и внедрение зависимостей предпочтительнее изменения состояния. Вместо того, чтобы изменять состояние потока, вы можете написать промежуточное программное обеспечение, которое применяет желаемое состояние к каждому запросу. Или вы можете внедрить желаемое состояние — все методы BCL, на которые влияет локаль, принимают параметр CultureInfo, что позволяет им работать для любой CultureInfo, а не только для сервера.

Panagiotis Kanavos 18.12.2020 09:51

@PanagiotisKanavos Только что ответил на вопрос "почему"

Paul Biggar 18.12.2020 12:19

Зачем тогда тег F#, если вы пытаетесь использовать OCaml? Что касается cheaply, кросс-вызов, вероятно, уже добавляет много накладных расходов. Что вы можете сделать, так это как-то проверить, инициализирована ли другая среда выполнения - сохранив что-то в локальном хранилище потока или сохранив идентификаторы инициализированного потока в статической очереди. Лучшее место для этого — промежуточное ПО, а не сам запрос.

Panagiotis Kanavos 18.12.2020 12:29

Что касается тега F#, я пишу код на F#, используя ASP.NET, и он вызывает старый код (находящийся в OCaml) через C API.

Paul Biggar 18.12.2020 19:45
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать 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
5
584
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

private var threadFooDict = new Dictionary<int, ThreadSpecificFoo>();
public void Foo(){
    var threadId = Thread.CurrentThread.ManagedThreadId;//for the managed thread
    //var threadId = AppDomain.GetCurrentThreadId();//for the OS thread
    if (!threadFooDict.ContainsKey(threadId))
        threadFooDict[threadId] = new ThreadSpecificFoo();
    var thisFoo = threadFooDict[threadId];
}

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

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

Это может быть дорогостоящей проблемой и не будет хорошо масштабироваться (вообще), если инициализация имеет какое-либо распределение ресурсов. Тем не менее, есть много способов добиться этого, например, параллельный словарь идентификатора потока или другое новое безопасное решение для потоков, которое может заключаться в использовании ThreadLocal.

Бессмысленный пример

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

private static readonly ThreadLocal<bool> ThreadLocal = new ThreadLocal<bool>(() =>
{
   Thread.Sleep(100);
   // dll.init
   return true;
});

private static bool Check()
{
   if (!ThreadLocal.IsValueCreated)
   { 
      Console.WriteLine("starting thread : " + Thread.CurrentThread.ManagedThreadId);
      return ThreadLocal.Value;
   }
   Console.WriteLine("Already Started : " + Thread.CurrentThread.ManagedThreadId);
   return false;
}

Тест

for (int i = 0; i < 10; i++)
   Task.Run(Check);

Console.ReadKey();

Выход

starting thread : 8
starting thread : 4
starting thread : 5
starting thread : 6
starting thread : 7
starting thread : 9
starting thread : 10
starting thread : 11
Already Started : 4
Already Started : 6

Обновление за комментарий

По сути, ThreadLocal запускается один и только один раз для каждого потока.

Чтобы сделать еще один шаг вперед, вы можете создать класс промежуточного программного обеспечения для каждого запроса и добавить его в свой конвейер:

public class CustomMiddleware
{
    private static readonly ThreadLocal<bool> ThreadLocal = new ThreadLocal<bool>(() =>
    {
       // dll.init
       // return anything you like
       return true;
    });
    
    private readonly RequestDelegate _next;

    public CustomMiddleware(RequestDelegate next) =>  _next = next;

    public async Task Invoke(HttpContext httpContext)
    {
        // use the value if you need, do anything you like really
        var value = ThreadLocal.Value
        
        await _next(httpContext);
    }
}

Применение

public void Configure(IApplicationBuilder app, ...)
{
    app.UseMiddleware<CustomMiddleware>();
}

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

Paul Biggar 18.12.2020 12:22

@PaulBiggar dll инициализируется только по одной на поток. я обновил ответ

TheGeneral 20.12.2020 02:13

А, теперь я понял, спасибо @TheGeneral!

Paul Biggar 20.12.2020 18:24

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