如何禁用正则表达式操作以防止在.NET 4.5中挂起?

Ahm*_*eed 15 .net regex performance .net-4.5

有时能够限制正则表达式操作的模式匹配持续时间可能是有用的.特别是,当使用用户提供的模式来匹配数据时,由于嵌套的量词和过多的反向跟踪(参见灾难性的回溯),模式可能表现出较差的性能.应用超时的一种方法是异步运行正则表达式,但这可能是乏味的并且使代码混乱.

根据.NET Framework 4.5 Developer Preview中的新功能,看起来有一种新的内置方法可以支持这一点:

能够限制正则表达式引擎在超时之前尝试解析正则表达式的时间.

我该如何使用此功能?另外,使用它时我需要注意什么?

注意:因为鼓励我,所以我会这个问题回答这个问题.

Ahm*_*eed 20

我最近研究了这个主题,因为它让我感兴趣,并将在这里讨论要点.有关MSDN文档在这里,你可以检查出Regex类看到新的重载的构造函数和静态方法.可以使用Visual Studio 11 Developer Preview运行代码示例.

Regex班接受TimeSpan指定的超时时间.您可以在应用程序中指定宏和微观级别的超时,它们可以一起使用:

  • "REGEX_DEFAULT_MATCH_TIMEOUT"使用AppDomain.SetData方法设置属性(宏应用程序范围)
  • 传递matchTimeout参数(微局部范围)

AppDomain设置该属性后,所有Regex操作都将使用该值作为默认超时.要覆盖应用程序范围的默认值,只需将matchTimeout值传递给正则表达式构造函数或静态方法.如果AppDomain未设置默认值matchTimeout且未指定,则模式匹配不会超时(即原始的.NET 4.5之前的行为).

有两个主要的例外处理:

  • RegexMatchTimeoutException:发生超时时抛出.
  • ArgumentOutOfRangeException:当" matchTimeout为负或大于约24天" 时抛出.此外,TimeSpan值为零将导致抛出此值.

尽管不允许使用负值,但有一个例外:接受-1 ms的值.在内部,Regex类接受-1 ms,这是Regex.InfiniteMatchTimeout字段的值,表示匹配不应该超时(即原始的.NET 4.5之前的行为).

使用matchTimeout参数

在下面的示例中,我将演示有效和无效的超时方案以及如何处理它们:

string input = "The quick brown fox jumps over the lazy dog.";
string pattern = @"([a-z ]+)*!";
var timeouts = new[]
{
    TimeSpan.FromSeconds(4),     // valid
    TimeSpan.FromSeconds(-10)    // invalid
};

foreach (var matchTimeout in timeouts)
{
    Console.WriteLine("Input: " + matchTimeout);
    try
    {
        bool result = Regex.IsMatch(input, pattern,
                                    RegexOptions.None, matchTimeout);
    }
    catch (RegexMatchTimeoutException ex)
    {
        Console.WriteLine("Match timed out!");
        Console.WriteLine("- Timeout interval specified: " + ex.MatchTimeout);
        Console.WriteLine("- Pattern: " + ex.Pattern);
        Console.WriteLine("- Input: " + ex.Input);
    }
    catch (ArgumentOutOfRangeException ex)
    {
        Console.WriteLine(ex.Message);
    }
    Console.WriteLine();
}
Run Code Online (Sandbox Code Playgroud)

使用Regex该类的实例时,您可以访问该MatchTimeout属性:

string input = "The English alphabet has 26 letters";
string pattern = @"\d+";
var matchTimeout = TimeSpan.FromMilliseconds(10);
var sw = Stopwatch.StartNew();
try
{
    var re = new Regex(pattern, RegexOptions.None, matchTimeout);
    bool result = re.IsMatch(input);
    sw.Stop();

    Console.WriteLine("Completed match in: " + sw.Elapsed);
    Console.WriteLine("MatchTimeout specified: " + re.MatchTimeout);
    Console.WriteLine("Matched with {0} to spare!",
                         re.MatchTimeout.Subtract(sw.Elapsed));
}
catch (RegexMatchTimeoutException ex)
{
    sw.Stop();
    Console.WriteLine(ex.Message);
}
Run Code Online (Sandbox Code Playgroud)

使用AppDomain属性

"REGEX_DEFAULT_MATCH_TIMEOUT"属性用于设置应用程序范围的默认值:

AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT",
                                TimeSpan.FromSeconds(2));
Run Code Online (Sandbox Code Playgroud)

如果将此属性设置为无效TimeSpan值或无效对象,TypeInitializationException则在尝试使用正则表达式时将抛出a.

有效属性值的示例:

// AppDomain default set somewhere in your application
AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT",
                                TimeSpan.FromSeconds(2));

// regex use elsewhere...
string input = "The quick brown fox jumps over the lazy dog.";
string pattern = @"([a-z ]+)*!";

var sw = Stopwatch.StartNew();
try
{
    // no timeout specified, defaults to AppDomain setting
    bool result = Regex.IsMatch(input, pattern);
    sw.Stop();
}
catch (RegexMatchTimeoutException ex)
{
    sw.Stop();
    Console.WriteLine("Match timed out!");
    Console.WriteLine("Applied Default: " + ex.MatchTimeout);
}
catch (ArgumentOutOfRangeException ex)
{
    sw.Stop();
}
catch (TypeInitializationException ex)
{
    sw.Stop();
    Console.WriteLine("TypeInitializationException: " + ex.Message);
    Console.WriteLine("InnerException: {0} - {1}",
        ex.InnerException.GetType().Name, ex.InnerException.Message);
}
Console.WriteLine("AppDomain Default: {0}",
    AppDomain.CurrentDomain.GetData("REGEX_DEFAULT_MATCH_TIMEOUT"));
Console.WriteLine("Stopwatch: " + sw.Elapsed);
Run Code Online (Sandbox Code Playgroud)

使用带有无效(负)值的上述示例将导致抛出异常.处理它的代码将以下消息写入控制台:

TypeInitializationException:'System.Text.RegularExpressions.Regex'的类型初始值设定项引发了异常.

InnerException:ArgumentOutOfRangeException - 指定的参数超出了有效值的范围.参数名称:AppDomain数据'REGEX_DEFAULT_MATCH_TIMEOUT'包含用于为System.Text.RegularExpressions.Regex指定默认匹配超时的无效值或对象.

在两个例子中ArgumentOutOfRangeException都没有抛出.为了完整性,代码显示了使用新的.NET 4.5 Regex超时功能时可以处理的所有异常.

覆盖AppDomain默认值

AppDomain通过指定matchTimeout值来覆盖默认值.在下一个示例中,匹配在2秒内超时,而不是默认值5秒.

AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT",
                                TimeSpan.FromSeconds(5));

string input = "The quick brown fox jumps over the lazy dog.";
string pattern = @"([a-z ]+)*!";

var sw = Stopwatch.StartNew();
try
{
    var matchTimeout = TimeSpan.FromSeconds(2);
    bool result = Regex.IsMatch(input, pattern,
                                RegexOptions.None, matchTimeout);
    sw.Stop();
}
catch (RegexMatchTimeoutException ex)
{
    sw.Stop();
    Console.WriteLine("Match timed out!");
    Console.WriteLine("Applied Default: " + ex.MatchTimeout);
}

Console.WriteLine("AppDomain Default: {0}",
    AppDomain.CurrentDomain.GetData("REGEX_DEFAULT_MATCH_TIMEOUT"));
Console.WriteLine("Stopwatch: " + sw.Elapsed);
Run Code Online (Sandbox Code Playgroud)

闭幕致辞

MSDN建议在所有正则表达式模式匹配操作中设置超时值.但是,他们并没有提请您注意这样做时需要注意的问题.我不建议设置AppDomain默认值并将其称为一天.您需要了解您的输入并了解您的模式.如果输入很大,或者模式很复杂,则应使用适当的超时值.这也可能需要测量您正常运行的正则表达式,以分配合理的默认值.任意为过去工作正常的正则表达式分配超时值可能会导致它在值不够长时中断.如果您认为可能过早地中止匹配尝试,请在分配值之前测量现有用法.

此外,此功能在处理用户提供的模式时很有用.然而,学习如何编写表现良好的正确模式非常重要.对其进行超时以弥补在正确的模式构建方面缺乏知识并不是一种好的做法.

  • 好写的.更好的是我在你的博客上发现的这个信息的扩展版本:[正则表达式引擎已更新,允许在.NET 4.5中超时](http://softwareninjaneer.com/blog/regex-engine-updated-to-allow-超时-在净4-5 /).由于你没有链接到博客文章,我想我会提到它:-). (2认同)