28

概述

我们有一个跨国网站,其中包含其服务的各个国家/地区的本地化内容。这种本地化是使用标准的 .Net 资源文件实现的。

当我们的 Web 应用程序在生产环境的负载下启动或回收时,有时它会显示特定国家/地区的错误资源。例如,英国站点可能会显示法语内容。

这将继续发生,直到重新启动应用程序。

细节

生产环境是 Windows Server 2012 上的 IIS 8。应用程序在 ASP.Net MVC 4 中实现。

应用程序通过传入的 URL 决定它所服务的语言环境。所以 www.mysite.com 将是英国英语 www.mysite.fr 将是法语等。

我们有一个通过 Web.config 注册的 IHttpModule 实现。在模块的 Init 方法中,它将处理程序附加到 BeginRequest 事件。在此方法中,将检查传入的 URL,并将线程的 CurrentUICulture 设置为适当的值。en-GB 表示 www.mysite.com,fr-FR 表示 www.mysite.fr 等。

该系统在大多数情况下运行良好。但是,有时当应用程序在接收请求时启动时,它会始终为某些资源文件提供错误的内容。

它会继续这样做,直到应用程序重新启动。它可能会再次重新开始提供不正确的内容。我们必须不断重启,直到它提供正确的内容,此时它将保持稳定。

分析

通过在启动期间向应用程序抛出请求(使用 Fiddler),我们已经能够在开发 PC 上本地重现这一点。该网站在英国版网站上显示了一些资源文件的德语内容。

在检查了我们代码中明显的罪魁祸首(HTTP 模块正确设置了 CurrentUICulture 并且在整个请求处理过程中保持正确)之后,我们开始查看资源管理器。

当应用程序在这种不正确的状态下启动时,我们检查了 ResourceManager 类的 _resourceSets 属性的内容。这是一个以 ISO 文化代码为关键字的字典。检查 en-GB 的内容,我们发现它确实包含来自德语版本的资源文件的资源字符串。

有时,当站点在接收请求时启动时,ResourceManager 类似乎为文化加载了错误的资源文件,或者它在其字典中错误地对文件进行了分类。

有没有其他人经历过这种行为,是否有人知道任何解决方法?

谢谢。

4

5 回答 5

5

这听起来像您的处理程序中存在线程安全问题。当您修改线程当前区域性时,您正在为可能正在处理多个请求的当前线程修改它。生成响应时,另一个请求可能会更改线程当前语言,从而为所有响应提供相同的语言。

我有一些建议你可以开始:

  1. 确保您的处理程序不可重复使用。我假设你已经实现了 IHttpHandler,为 IsReusable 属性返回 false,因为多个线程应该同时命中它,并且每个请求都会创建一个新实例。
  2. 不要使用处理程序......处理程序不是这样的理想解决方案,设置线程文化的首选位置是在Application_AcquireRequestState中,它将为每个请求正确触发而不会重叠。
  3. 改用路由处理程序:http: //adamyan.blogspot.com/2010/07/addition-to-aspnet-mvc-localization.html

如果没有看到你所说的处理程序,剩下的只能是关于为什么响应共享线程文化的猜测。问题很容易出在您正在使用的字典中,这本身就不是线程安全的。

于 2013-07-23T09:37:30.960 回答
2

我们最近遇到了同样的问题。这似乎是 ResourceManager(或其帮助代码)中的竞争条件。我在https://bitbucket.org/onyxmaster/resmanrc上整理了一份报告。此外,我在 MS Connect 站点上提交了一个错误,网址为https://connect.microsoft.com/VisualStudio/feedback/details/806505/

PS我不确定这是否算作一个答案,因为我知道没有解决方法,但至少现在有一个重现和一个错误报告。

于 2013-10-24T23:28:20.107 回答
1

对于遇到此问题的其他人,我从未解决此问题的根本原因,但确实找到了解决方法。当应用程序启动时,我有一个例程按顺序浏览每个资源文件,并以每种支持的语言从每个文件中请求一个资源。以这种方式以单线程方式“接触”每个文件似乎每次都可以正确加载所有资源。

于 2016-01-14T17:43:47.867 回答
0

你在使用异步 MVC 操作吗?如果是,则当您等待调用时(将 ConfigureAwait 设置为 false),处理线程在进行时将被重用。“返回”线程(在等待调用之后执行代码的线程)不一样,可能会丢失之前设置的所有属性。ConfigureAwait 必须设置为 false 以防止死锁,因此没有立即解决方案。

要检查的另一件事是缓存,如果您使用 [Cache] 属性或缓存资源管理器。

于 2013-07-26T05:58:46.903 回答
0

我从其他资源中阅读了这个答案。并希望将 ResourceManager 包装如下,以便新的 RexourceManager() 不会在加载的多线程调用下进行竞争:

public sealed class LocalizationHandler
{
   [ThreadStatic]
   private static ResourceManager _manager
   private readonly ConcurrentBag<ResourceManager> 
       _localizationIdentityCollection = new ConcurrentBag<ResourceManager>();

   private LocalizationHandler(){}

   public static LocalizationHandler Load(ResourceType source)
   {
      switch (source)
      {
          case typeA:
              //check if resource exist in the concurrent bag or create a new one
           _manager = getManager();
           break;
      }
      return this;
   }

   public string Get(string key)
   {
       return _manager.Get(key)
   }
}

然后你可以这样调用:

LocalizationHandler.Load(ResourceType.TypeA).Get("your resource Key String")

此外,您可以将类型安全枚举用于资源类型,将名称用于资源名称,并将资源管理器与文化信息一起包装到私有类中,并将其保存在并发包中。

于 2017-08-02T20:53:11.593 回答