Исключение истекло время ожидания в коде исключительно с использованием операторов using

У меня есть многопоточное приложение, которое общается с SQL-сервером через Linq to Sql. Приложение нормально работает на четырехъядерном компьютере (Intel I-7), когда количество потоков искусственно поддерживается на уровне 8:

            Parallel.ForEach(allIds, 
                    new ParallelOptions { MaxDegreeOfParallelism = 8 },
                    x => DoTheWork(x));

Когда количество потоков остается на усмотрение системы:

                Parallel.ForEach(allIds, x => DoTheWork(x));

По прошествии некоторого времени я получаю следующее исключение:

Истекло время ожидания. Период тайм-аута истек до получения соединения из пула. Это могло произойти из-за того, что все соединения в пуле использовались и был достигнут максимальный размер пула.

В моем приложении всего два шаблона для вызова SQL:

первый:

    using (var dc = new MyDataContext())
    {
        //do stuff
        dc.SafeSubmitChanges();
    }

второй:

        using (var dc = new MyDataContext())
        {
            //do some other stuff
            DoStuff(dc);
        }

.....
    private void DoStuff(DataContext dc)
    {
       //do stuff
       dc.SafeSubmitChanges();
    }

Я решил задросселировать вызовы с помощью такой логики:

public static class DataContextExtention
{
    public const int SQL_WAIT_PERIOD = 5000;
    public static void SafeSubmitChanges(this DataContext dc)
    {
        try
        {
            dc.SubmitChanges();
        }
        catch (Exception e)
        {
            if (e.Message ==
                "Timeout expired.  The timeout period elapsed prior to obtaining a connection from the pool.  This may have occurred because all pooled connections were in use and max pool size was reached.")
            {
                System.Data.SqlClient.SqlConnection.ClearAllPools();
                System.Threading.Thread.Sleep(SQL_WAIT_PERIOD);
                dc.SafeSubmitChanges();
            }
            else
            {
                throw;
            }
        }
    }
}

Это не имело абсолютно никакого значения. Как только приложение генерирует первое исключение такого типа, все виды случайных мест в приложении (даже строки кода, не имеющие ничего общего с SQL-сервером) начинают генерировать это исключение.

Q1: Разве не предполагается, что использование оператора using с религиозной точки зрения защищает именно от этого сценария?

Q2: Что не так и как это исправить?

Примечание. Существует около 250 000 идентификаторов. Я также тестировал на MaxDegreeOfParallelism = 16, и у меня такое же исключение.


person Barka    schedule 14.05.2014    source источник
comment
Я не знаю ответа, но какого черта вам нужно сравнивать e.Message с постоянной строкой вместо проверки типа e? Это недопустимо, тем более что исключение может быть создано на любом языке.   -  person Tarec    schedule 14.05.2014
comment
Сколько итераций выполняется в этой параллельной foreach? Вероятно, что по какой-то причине вы удаляете DataContext, но соединение не закрывается. Посмотрим, сможешь ли ты поместить dc.Connection.Close() в блок using.   -  person Evan L    schedule 14.05.2014
comment
Я думаю, что максимальный размер пула для соединений равен 100. Попробуйте установить для своего MaxDegreeOfParallelism значение 100, посмотрите, работает ли это, а затем попробуйте 101 и посмотрите, не удастся ли это. Даже если ваши DataContext правильно утилизируются (а я думаю, что это так), после того, как вы исчерпали пул соединений, дополнительные контексты должны ждать освобождения соединения. Поскольку время ожидания операций с базой данных относительно велико, вполне возможно, что Parallel.ForEach выполняет более 100 одновременных операций.   -  person hatchet - done with SOverflow    schedule 19.05.2014
comment
@hatchet, он терпит неудачу при MaxDegreeOfParallelism = 16. Мы даже близко не приближаемся к 100, пока он не выйдет из строя.   -  person Barka    schedule 19.05.2014
comment
Исключения во всевозможных случайных местах в многопоточном приложении почти всегда возникают из-за неспособности должным образом синхронизировать доступ к ресурсам, совместно используемым несколькими потоками.   -  person Joe    schedule 19.05.2014
comment
Можете ли вы включить строку подключения (с фиктивным пользователем и паролем, если они есть)? Попробуйте добавить Min Pool Size = 16 и проведите тест с 16 степенями параллелизма. Кроме того, проверьте возможность того, что Sql Server ограничивает соединения stackoverflow.com/questions/1499718/   -  person hatchet - done with SOverflow    schedule 19.05.2014
comment
Можете ли вы предоставить полную трассировку стека?   -  person Maarten    schedule 19.05.2014
comment
О, думаю, речь идет о пуле соединений для базы данных. Можете ли вы предоставить настройки для базы данных из конфигурации вашего веб-сайта / приложения?   -  person Cosmin Vană    schedule 21.05.2014
comment
Попробуйте включить трассировку ado.net и опубликуйте результаты! developer.com/net/csharp/article. php / 3723011 /   -  person Jonas Jämtberg    schedule 22.05.2014


Ответы (4)


Я полагаю, это зависит от того, сколько элементов находится в allIds. Если Parallel.ForEach создает слишком много параллельных одновременных задач, возможно, каждая из них пытается открыть соединение с базой данных (параллельно) и, таким образом, исчерпывает пул соединений и делает невозможным предоставление соединений для всех параллельных задач, которые запрашивают новые соединения.

Если выполнение запроса пула соединений занимает больше времени, чем тайм-аут, это сообщение об ошибке имеет смысл. Таким образом, когда вы устанавливаете MaxDegreeOfParallelism = 8, у вас не более 8 одновременных задач и, следовательно, не более 8 подключений, «извлеченных» из пула. Перед завершением задачи (и у Parallel.ForEach теперь есть доступный слот для запуска новой задачи) соединение возвращается обратно в пул, так что когда Parallel.ForEach запускает следующий элемент, пул соединений может удовлетворить следующий запрос на соединение, и, таким образом, вы не не возникает проблемы, когда вы искусственно ограничиваете параллелизм.

РЕДАКТИРОВАТЬ 1

Предложение @Hatched, приведенное выше, находится на правильном пути - увеличьте размер пула. Однако есть нюанс. Скорее всего, ваше узкое место на самом деле не в вычислительной мощности, а в активности базы данных. Я подозреваю (предположительно, предположение), что во время разговора с базой данных поток мало что может сделать и блокируется (или переключается на другую задачу). Таким образом, пул потоков видит, что есть еще нерешенные задачи, но ЦП не используется (из-за невыполненных операций ввода-вывода), и поэтому решает взять на себя больше задач из-за доступной нехватки ресурсов ЦП. Это, конечно, еще больше насыщает узкое место и возвращает его на круги своя. Таким образом, даже если вы увеличите размер пула соединений, вы, скорее всего, будете продолжать работать на стене, пока размер вашего пула не станет равным вашему списку задач. Таким образом, вы действительно можете захотеть иметь ограниченный параллелизм, чтобы он никогда не исчерпывал пул потоков (и точную настройку, увеличивая / уменьшая пул потоков в зависимости от нагрузки БД и т. Д.).

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

person LB2    schedule 19.05.2014
comment
Да, но при MaxDegreeOfParallelism = 8 я не использую всю вычислительную мощность оборудования. Мне нужно дросселировать иначе, чтобы не мешать дроссельной заслонке. Вот почему я добавил код thottle выше, но это не помогло. Остается вопрос. - person Barka; 19.05.2014
comment
@ user277498 См. EDIT 1 в сообщении выше. - person LB2; 19.05.2014

Я думал, что следующее может помочь, по моему опыту работы с Oracle, пул соединений с БД уже вызывал у меня проблемы. Поэтому я подумал, что может быть аналогичная проблема с пулом соединений SQL Server. Иногда полезной информацией является знание настроек подключения по умолчанию и наблюдение за активностью подключения к базе данных.

Если вы используете Sql Server 8, пул подключений SQL по умолчанию равен 100. Время ожидания по умолчанию составляет 15 секунд. Я бы хотел, чтобы администратор SQL отслеживал, сколько подключений вы делаете при запуске приложения, и смотрел, загружаете ли вы сервер БД. Может быть, добавить еще и счетчики производительности. Поскольку это похоже на исключение SQL Server, я хотел бы получить некоторые показатели, чтобы увидеть, что происходит. Вы также можете использовать intellitrace, чтобы увидеть активность БД.

Ссылка Intellitrace: http://www.dotnetcurry.com/showarticle.aspx?ID=943 < / а>

Ссылка на пул подключений Sql Server 2008: http://msdn.microsoft.com/en-us/library/8xx3tyca%28v=vs.110%29.aspx

Ссылка на счетчики производительности: http://msdn.microsoft.com/en-us/library/ms254503%28v=vs.110%29.aspx

person n4gy3    schedule 19.05.2014

Я мог бы быть далеко от цели здесь, но мне интересно, не вызвана ли проблема как побочный эффект этого факта о пуле соединений (Взято из здесь, выделено мной):

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

  • Другими словами, дело не в том, что у вас заканчиваются соединения как таковые, а в том, что что-то дает сбой в одной или нескольких параллельных операциях, возможно, из-за того, что плохая таблица прогибается под давлением параллельной записи - вы профилировали, что происходит на стороне базы данных, чтобы увидеть, есть ли какие-либо проблемы с конкуренцией за таблицу во время операции?

  • Это может вызвать запуск других запросов на подключение к резервному копированию из-за «штрафа», описанного выше; отсюда и исключения, и как только вы начнете их получать, ваш SafeSubmit метод может только ухудшить ситуацию, потому что он продолжает повторять уже заблокированную операцию.

  • Это объяснение также в значительной степени поддерживает идею о том, что настоящим узким местом здесь является база данных и что, возможно, не стоит пытаться забить таблицу неограниченным параллельным вводом-выводом; Лучше измерить и придумать максимальный DOP, основанный на характеристиках того, что может выдержать база данных (которые могут отличаться для разного оборудования)

Кроме того, что касается вашего первого вопроса, using гарантирует только то, что ваш DataContext объект будет автоматически Dispose()d, когда он выйдет за пределы области видимости, поэтому он вовсе не предназначен для защиты в этом сценарии - все это синтаксический сахар для

try
{
    var dc = new DataContext();
    //do stuff with dc
}
finally
{
    dc.Dispose();
}

и в данном случае это не гарантия того, что (слишком) много DataContexts в настоящее время пытаются одновременно подключиться к базе данных.

person Stephen Byrne    schedule 26.05.2014

Вы уверены, что не столкнулись с утечками соединения? Ознакомьтесь с принятым ответом на странице эту ссылку

Более того, вы уже установили MultipleActiveResultSets = true?

Из MSDN:

Если установлено значение true, приложение может поддерживать несколько активных наборов результатов (MARS). Если установлено значение false, приложение должно обработать или отменить все наборы результатов из одного пакета, прежде чем оно сможет выполнить любой другой пакет в этом соединении. Признанные ценности бывают истинными и ложными.

Для получения дополнительной информации см. Несколько активных наборов результатов (MARS).

person Mauro Sampietro    schedule 22.05.2014
comment
благодаря. Поскольку я использую исключительно операторы using, утечки соединения быть не должно. Правильно? Я включу МАРС и посмотрю, имеет ли это значение, и дам вам знать. - person Barka; 24.05.2014