У меня есть HTTP-сервер ASP.NET Core, работающий в .NET 5. Библиотеку, которую я использую, необходимо инициализировать один раз для каждого потока. В идеале я мог бы добавить какой-нибудь обратный вызов, чтобы вызывать код инициализации, когда веб-сервер ASP.NET запускает поток. Существует ли такая вещь?
Причина этого в том, что мне нужно сделать вызовы в какой-то старый код в среде выполнения OCaml, а OCaml требует, чтобы каждый поток был зарегистрирован для вызова среды выполнения OCaml. В настоящее время я делаю это один раз для каждого запроса, но вместо этого я хочу сделать это как можно дешевле.
Обновление: похоже, что ASP.NET использует пул потоков .NET по умолчанию. Пока не знаю, что делать с этой информацией, но если есть способ запустить этот обратный вызов для всех потоков в пуле потоков, это сработает для меня.
Вы используете тег F#
, поэтому неудивительно, что неизменяемость и внедрение зависимостей предпочтительнее изменения состояния. Вместо того, чтобы изменять состояние потока, вы можете написать промежуточное программное обеспечение, которое применяет желаемое состояние к каждому запросу. Или вы можете внедрить желаемое состояние — все методы BCL, на которые влияет локаль, принимают параметр CultureInfo
, что позволяет им работать для любой CultureInfo, а не только для сервера.
@PanagiotisKanavos Только что ответил на вопрос "почему"
Зачем тогда тег F#, если вы пытаетесь использовать OCaml? Что касается cheaply
, кросс-вызов, вероятно, уже добавляет много накладных расходов. Что вы можете сделать, так это как-то проверить, инициализирована ли другая среда выполнения - сохранив что-то в локальном хранилище потока или сохранив идентификаторы инициализированного потока в статической очереди. Лучшее место для этого — промежуточное ПО, а не сам запрос.
Что касается тега F#, я пишу код на F#, используя ASP.NET, и он вызывает старый код (находящийся в OCaml) через C API.
Технически это не отвечает на ваш вопрос, но у вас может быть список, хэш или словарь зарегистрированных потоков. Всякий раз, когда вызываются ваши основные методы, в начале проверяйте, был ли уже подготовлен этот конкретный поток.
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>();
}
Я уточнил вопрос, чтобы отразить, что я хочу сделать это только один раз для каждого потока, а не для каждого запроса.
@PaulBiggar dll инициализируется только по одной на поток. я обновил ответ
А, теперь я понял, спасибо @TheGeneral!
Почему? Каждый запрос обслуживается отдельным потоком из пула потоков. Если вы используете внутрипроцессное развертывание IIS, пул предоставляется IIS. Изменение потока в пуле (например, путем изменения его CultureInfo) может вызвать непредвиденные проблемы при следующем использовании.