IStringLocalizer
问题代码示例:
public class HomeController : Controller
{private readonly IStringLocalizer<HomeController> _localizer;private readonly ILogger<HomeController> _logger;public HomeController(IStringLocalizer<HomeController> localizer, ILogger<HomeController> logger){_localizer = localizer;_logger = logger;}public IActionResult Index(){// 这里一切正常,_localizer 根据请求语系(如en-US)工作var message = _localizer["Hello"].Value;_logger.LogInformation("主线程消息: {Message}", message); // 输出:Hello// 开启一个后台任务Task.Run(() =>{// 这里出问题了!var backgroundMessage = _localizer["Hello"].Value;_logger.LogInformation("后台任务消息: {Message}", backgroundMessage); // 输出:你好});return View();}
}
[ASP.NET]Core的HttpContext
(以及与之关联的语系信息)是与当前执行线程关联的,但它并不随Task
的调度而自动流动。
async/await
的“文明人”行为: 在纯粹的async/await
代码中,会有一个叫做ExecutionContext
的东西在背后默默工作。它像一个贴心的管家,会跟随await
前后的代码流动,其中就包含了当前线程的语系(CultureInfo.CurrentCulture
和CultureInfo.CurrentUICulture
)。所以,在大多数异步操作中,语系信息得以保持。Task.Run
的“野蛮”开局:Task.Run
用于将一个工作项丢到线程池队列中。线程池线程在开始执行这个任务时,它是一个“干净”的状态。它的语系默认是线程池的语系,而线程池的语系通常就是系统的默认语系(例如zh-CN
)。ExecutionContext
虽然会流动,但默认情况下,Task.Run
不会捕获并流动调用线程的“语系”部分(更准确地说,在.NET Core/.NET 5+中,语系默认不包含在ExecutionContext
的流动中,除非应用了兼容性开关)。
解决:在启动新Task之前,手动捕获当前的语系信息,并在任务内部显式地应用它。
警钟:在ASP.NET Core应用中,任何脱离原始HTTP请求上下文的后台操作(如Task.Run
、BackgroundService
、IHostedService
),都必须显式处理语系等上下文信息的传递问题。