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

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

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

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

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

Примечание. Я задаю и отвечаю на этот вопрос, так как он рекомендовал.к югу >

Ответ 1

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

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

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

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

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

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

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

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