Транзакция Entity Framework с несколькими потоками

#c# #multithreading #entity-framework #transactions #transactionscope

#c# #многопоточность #entity-framework #транзакции #transactionscope

Вопрос:

У меня есть приложение, выполняющее несколько потоков. Потоки не совместно используют ObjectContext (у каждого потока свой собственный — я знаю, что они не потокобезопасны).

Однако все потоки работают в рамках общей транзакции. Исходный поток создает TransactionScope, и каждый поток, который он порождает, создает TransactionScope, используя DependentTransaction от транзакции в основном потоке.

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

 System.Data.EntityException occurred
  Message=An error occurred while closing the provider connection. See the inner exception for details.

  InnerException: System.Transactions.TransactionException
       Message=The operation is not valid for the state of the transaction.
       Source=System.Transactions
       StackTrace:
            at System.Transactions.TransactionStatePSPEOperation.get_Status(InternalTransaction tx)
            at System.Transactions.TransactionInformation.get_Status()
            at System.Data.ProviderBase.DbConnectionInternal.CloseConnection(DbConnection owningObject, DbConnectionFactory connectionFactory)
            at System.Data.SqlClient.SqlInternalConnection.CloseConnection(DbConnection owningObject, DbConnectionFactory connectionFactory)
            at System.Data.SqlClient.SqlConnection.Close()
            at System.Data.EntityClient.EntityConnection.StoreCloseHelper()
       InnerException: 
  

Я знаю только, что они выполняются одновременно, потому что, когда я запускаю свои модульные тесты в режиме отладки и появляется это исключение, если я смотрю на разные запущенные потоки, я всегда вижу, что по крайней мере один другой поток остановлен при операции ObjectContext.

Кроме того, после некоторого чтения я попытался добавить multipleactiveresultsets=False в свою строку подключения, и это ничего не изменило.

Это ошибка в Entity Framework?

Ответ №1:

Проблема описана здесь:

http://www.b10g.dk/2007/09/07/dependenttransaction-and-multithreading/

Достаточно просто заблокировать вызовы SaveChanges и Refresh, но для того, чтобы убедиться, что блокировки происходят во время выполнения запроса, мне пришлось создать фиктивный поставщик запросов, который блокирует при выполнении запросов. Мне действительно не следовало этого делать. Entity Framework должна была быть достаточно надежной, чтобы справиться с этим «из коробки» … особенно учитывая, что вы не предназначены для создания собственного соединения.

Вот код для оболочки поставщика запросов. Сами IQueryables и базовый класс QueryProvider являются простыми многоразовыми реализациями, основанными здесь http://blogs.msdn.com/b/mattwar/archive/2007/07/30/linq-building-an-iqueryable-provider-part-i.aspx

     /// <summary>
    /// A wrapper for queries executed by EF.
    /// </summary>
    internal class EntityFrameworkQueryProvider : QueryProvider
    {    
        protected override object Execute(Expression expression)
        {
            try
            {
                // this is required due to a bug in how EF multi-threads when Transactions are used.
                if (Transaction.Current != null) Monitor.Enter(EntityFrameworkExtensions.SyncRoot);

                // enumerate is a simple extension method that forces enumeration of the IQueryable, thus making it actually get executed during the lock
                return Expression.Lambda(expression).Compile().DynamicInvoke().Enumerate();
            }
            finally
            {
                if (Transaction.Current != null) Monitor.Exit(EntityFrameworkRepositoryProvider.SyncRoot);
            }
        }
    }
  

Комментарии:

1. Ссылка, на которой вы основываете свой ответ, больше не работает. Не могли бы вы, пожалуйста, предоставить дополнительную информацию о первопричине?

2. SyncRoot, который вы используете для ввода блокировки, отличается от того, который вы используете при выходе.

Ответ №2:

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

Другая возможность заключается в том, что «родительская» транзакция завершается или удаляется до того, как потоки завершат свою транзакцию. Убедитесь, что ваша асинхронная работа выполнена, прежде чем вы покинете основной TranscationScope (хотя это может быть настроено на блокировку незавершенных дочерних транзакций).

Извините, у меня нет больше того, что вы описали.

Удачи, Майкл

Комментарии:

1. Все мои транзакции в новых потоках являются зависимыми клонами, и родительский поток не завершает транзакцию раньше любого из дочерних потоков. Есть еще мысли? Спасибо.