Как мне тайм-аут операций Regex, чтобы предотвратить зависание в .NET 4.5? - PullRequest
12 голосов
/ 01 октября 2011

Иногда бывает полезно ограничить продолжительность сопоставления с шаблоном операций регулярного выражения.В частности, при работе с пользовательскими шаблонами для сопоставления данных шаблон может демонстрировать низкую производительность из-за вложенных квантификаторов и чрезмерного обратного отслеживания (см. катастрофическое обратное отслеживание ).Один из способов применения тайм-аута - запускать регулярное выражение асинхронно, но это может быть утомительным и загромождать код.

В соответствии с , что нового в .NET Framework 4.5 Developer Preview , похоже,Существует новый встроенный подход для поддержки этого:

Возможность ограничить время, в течение которого механизм регулярных выражений будет пытаться разрешить регулярное выражение до истечения времени его ожидания.

Как я могу использовать эту функцию?Кроме того, что мне нужно знать при его использовании?

Примечание: я задаю и отвечаю на этот вопрос, поскольку этопоощряла .

1 Ответ

18 голосов
/ 01 октября 2011

Я недавно исследовал эту тему, так как она заинтересовала меня и расскажет об основных моментах здесь. Соответствующая документация MSDN доступна здесь , и вы можете проверить класс Regex, чтобы увидеть новые перегруженные конструкторы и статические методы. Примеры кода могут быть запущены с Visual Studio 11 Developer Preview .

Класс Regex принимает значение TimeSpan для указания времени ожидания. Вы можете указать тайм-аут на макро- и микроуровне в вашем приложении, и они могут использоваться вместе:

  • Установите свойство "REGEX_DEFAULT_MATCH_TIMEOUT", используя метод AppDomain.SetData (область действия макроса)
  • Передать параметр matchTimeout (микро локализованная область)

Когда установлено свойство AppDomain, все операции Regex будут использовать это значение в качестве времени ожидания по умолчанию. Чтобы переопределить значение по умолчанию для всего приложения, вы просто передаете значение matchTimeout в конструктор регулярных выражений или статический метод. Если AppDomain по умолчанию не установлено, а matchTimeout не указано, то сопоставление с образцом не прекратится (то есть исходное поведение до .NET 4.5).

Существует 2 основных исключения:

  • RegexMatchTimeoutException: генерируется при возникновении тайм-аута.
  • ArgumentOutOfRangeException: бросается, когда «matchTimeout отрицательно или превышает приблизительно 24 дня». Кроме того, значение TimeSpan, равное нулю, приведет к его выбрасыванию.

Несмотря на то, что отрицательные значения недопустимы, есть одно исключение: принимается значение -1 мс. Внутренне класс Regex принимает -1 мс, что является значением поля 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();
}

При использовании экземпляра класса 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);
}

Использование свойства 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 по умолчанию и называть его днем. Вы должны знать ваш вклад и знать ваши шаблоны. Если вход большой или шаблон сложный, следует использовать соответствующее значение времени ожидания. Это может также повлечь за собой измерение вашего критически эффективного использования регулярных выражений для назначения нормальных значений по умолчанию. Произвольное назначение значения времени ожидания для регулярного выражения, которое раньше работало нормально, может привести к его разрыву, если значение недостаточно велико. Измерьте существующее использование перед назначением значения, если вы считаете, что оно может прервать попытку сопоставления слишком рано.

Кроме того, эта функция полезна при обработке пользовательских шаблонов. Тем не менее, важно научиться писать правильные шаблоны, которые хорошо работают. Сокращение времени ожидания для восполнения недостатка знаний в правильном построении паттерна не является хорошей практикой.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...