【问题标题】:Using RazorEngine to parse Razor templates concurrently使用 RazorEngine 并发解析 Razor 模板
【发布时间】:2011-06-22 17:51:57
【问题描述】:

我在 MVC 3 Web 应用程序中使用 RazorEngine 库 (http://razorengine.codeplex.com/) 使用 Razor 模板语言解析字符串(不是视图)。

一般来说,这很好用。但是,当多个用户同时访问解析 Razor 模板的代码时,我偶尔会看到看起来像是在内部 Razor 编译器中发生的错误(请参阅下面的两个)。我无法解释这些错误,但我猜我调用 Razor 编译器的方式不是并发安全的。

这是 Razor 编译器的已知问题吗?普通的 Razor 视图 (.cshtml) 如何不会遇到此问题?有没有比将我的应用程序对 Razor.Parse 的所有调用包装在互斥锁中更好的解决方法?

我的调用代码如下,只是对Razor.Parse的简单包装:

    protected string ParseTemplate<T>(string templateString, T model)
    {
        //This binderAssembly line is required by NUnit to prevent template compilation errors
        var binderAssembly = typeof(Microsoft.CSharp.RuntimeBinder.Binder).Assembly;
        var result = Razor.Parse(templateString, model);
        return result;
    }

错误一:

System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: chunkLength
at System.Text.StringBuilder.ToString()
at System.Web.Razor.Generator.RazorCodeGenerator.BlockContext.MarkEndGeneratedCode()
at System.Web.Razor.Generator.RazorCodeGenerator.WriteBlock(BlockContext block)
at System.Web.Razor.Parser.ParserContext.FlushNextOutputSpan()
at System.Web.Razor.Parser.ParserContext.StartBlock(BlockType blockType, Boolean outputCurrentBufferAsTransition)
at System.Web.Razor.Parser.ParserBase.ParseComment()
at System.Web.Razor.Parser.ParserBase.TryParseComment(SpanFactory previousSpanFactory)
at System.Web.Razor.Parser.ParserBase.ParseBlockWithOtherParser(SpanFactory previousSpanFactory, Boolean collectTransitionToken)
at System.Web.Razor.Parser.HtmlMarkupParser.TryStartCodeParser(Boolean isSingleLineMarkup, Boolean documentLevel)
at System.Web.Razor.Parser.HtmlMarkupParser.ParseRootBlock(Tuple`2 nestingSequences, Boolean caseSensitive)
at System.Web.Razor.Parser.RazorParser.Parse(LookaheadTextReader input, ParserVisitor visitor)
at System.Web.Razor.RazorTemplateEngine.GenerateCodeCore(LookaheadTextReader input, String className, String rootNamespace, String sourceFileName, Nullable`1 cancelToken)
at System.Web.Razor.RazorTemplateEngine.GenerateCode(TextReader input, String className, String rootNamespace, String sourceFileName, Nullable`1 cancelToken)
at System.Web.Razor.RazorTemplateEngine.GenerateCode(TextReader input)
at RazorEngine.Compilation.CompilerServiceBase.GetCodeCompileUnit(String className, String template, ISet`1 namespaceImports, Type templateType, Type modelType)
at RazorEngine.Compilation.DirectCompilerServiceBase.Compile(TypeContext context)
at RazorEngine.Compilation.DirectCompilerServiceBase.CompileType(TypeContext context)
at RazorEngine.Templating.TemplateService.CreateTemplate(String template, Type modelType)
at RazorEngine.Templating.TemplateService.Parse[T](String template, T model, String name)
at RazorEngine.Razor.Parse[T](String template, T model, String name)

错误二:

System.ObjectDisposedException: Cannot read from a closed TextReader.
at System.IO.StringReader.Read()
at System.Web.Razor.Text.BufferingTextReader.NextCharacter()
at System.Web.Razor.Text.BufferingTextReader.Read()
at System.Web.Razor.Parser.ParserContext.AcceptCurrent()
at System.Web.Razor.Parser.HtmlMarkupParser.ParseRootBlock(Tuple`2 nestingSequences, Boolean caseSensitive)
at System.Web.Razor.Parser.RazorParser.Parse(LookaheadTextReader input, ParserVisitor visitor)
at System.Web.Razor.RazorTemplateEngine.GenerateCodeCore(LookaheadTextReader input, String className, String rootNamespace, String sourceFileName, Nullable`1 cancelToken)
at System.Web.Razor.RazorTemplateEngine.GenerateCode(TextReader input, String className, String rootNamespace, String sourceFileName, Nullable`1 cancelToken)
at System.Web.Razor.RazorTemplateEngine.GenerateCode(TextReader input)
at RazorEngine.Compilation.CompilerServiceBase.GetCodeCompileUnit(String className, String template, ISet`1 namespaceImports, Type templateType, Type modelType)
at RazorEngine.Compilation.DirectCompilerServiceBase.Compile(TypeContext context)
at RazorEngine.Compilation.DirectCompilerServiceBase.CompileType(TypeContext context)
at RazorEngine.Templating.TemplateService.CreateTemplate(String template, Type modelType)
at RazorEngine.Templating.TemplateService.Parse[T](String template, T model, String name)
at RazorEngine.Razor.Parse[T](String template, T model, String name)

【问题讨论】:

    标签: c# asp.net-mvc concurrency razor


    【解决方案1】:

    更新: 根据他们团队的blog post,最新版本 3.x (on Github) 现在是线程安全的。我没有审查其线程安全的真实性,但假设它已正确实施。请考虑此答案的其余部分仅用于历史目的。


    从代码来看,这个项目看起来不是远程线程安全的。

    Razor.Parse

    public static string Parse<T>(string template, T model, string name = null)
    {
        return DefaultTemplateService.Parse<T>(template, model, name);
    }
    

    TemplateService.Parse

    public string Parse<T>(string template, T model, string name = null)
    {
        var instance = GetTemplate(template, typeof(T), name);
        ...
    }
    

    TemplateService.GetTemplate

    internal ITemplate GetTemplate(string template, Type modelType, string name)
    {
        if (!string.IsNullOrEmpty(name))
            if (templateCache.ContainsKey(name))
                return templateCache[name];
    
        var instance = CreateTemplate(template, modelType);
    
        if (!string.IsNullOrEmpty(name))
            if (!templateCache.ContainsKey(name))
                templateCache.Add(name, instance);
    
        return instance;
    }
    

    所以,Razor.Parse 是一个静态方法。 DefaultTemplateServiceRazor 上的静态属性,ParseGetTemplate 是实例方法,但由于静态 DefaultTemplateService 而有效地静态调用。这意味着所有线程都经过同一个实例并经过GetTemplate。你会注意到GetTemplate 改变状态(templateCache)而不获取任何锁。因此,这段代码不是线程安全的。

    【讨论】:

    • 我看到 RazorEngine 代码正在做非线程安全的事情,但我不确定这是否真的影响了我的代码。通过查看代码,当 name 为 null 时,您发布的非线程安全操作的特定实例被绕过 - 一旦我们到达 System.Web.Razor,在 RazorEngine 代码之后,实际错误似乎都发生了。
    • 实际上,我的意思是整个项目可能不是线程安全的。一般来说,如果您看到 一个 非线程安全代码的示例,那么很可能开发人员一开始就没有准备好在代码库的其余部分中处理多个线程。
    • 例如,DirectCompilerServiceBase 的方法 Compile 在您的第二个堆栈跟踪中。 一个 DirectCompilerServiceBase 实例用于所有调用。 (它是静态 TemplateService 上的实例字段)DirectCompilerServiceBase 有一个 instance 字段,其中包含 CodeDomProviderCodeDomProvider 不是线程安全的。请记住,处理多线程情况时的经验法则是默认情况下所有类都应该被假定为非线程安全的。几乎在每个类具有可变字段的情况下,它都不是线程安全的。
    • 您在 MVC 中看不到 Razor 的这些问题的原因是,每个请求都会调用一次 Razor 编译器,并且每个请求都由单个线程提供服务,因此您看到的并发问题不会'不会发生。
    • 那时你可能想检查一下 Nuget,我们推送了 v3.1.0 但几天前......现在这是一个相当老的问题了。?
    猜你喜欢
    • 1970-01-01
    • 2016-10-08
    • 2012-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多