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

时间:2011-09-30 21:48:58

标签: .net regex performance .net-4.5

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

根据what's new in the .NET Framework 4.5 Developer Preview,看起来有一种新的内置方法可以支持这一点:

  

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

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

注意:自asking and answering以来,我it's encouraged这个问题。

1 个答案:

答案 0 :(得分:18)

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

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

  • 使用AppDomain.SetData method(宏应用程序范围)
  • 设置"REGEX_DEFAULT_MATCH_TIMEOUT"属性
  • 传递matchTimeout参数(微型本地化范围)

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

要处理两个主要的例外情况:

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

尽管不允许使用负值,但有一个例外:接受-1 ms的值。在内部,Regex类接受-1 ms,这是Regex.InfiniteMatchTimeout field的值,表示匹配不应该超时(即原始的.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();
}

使用Regex课程的实例时,您可以访问MatchTimeout property

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);
}

使用AppDomain属性

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

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

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

有效属性值的示例:

// 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);

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

  

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);

结束备注

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

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