Обзор
Корень вашей проблемы в том, что L2S DataContext, как и ObjectContext Entity Framework, не является поточно-ориентированным. Как объясняется в на этом форуме MSDN , поддержка асинхронных операций в решениях .NET ORM все еще ожидается, начиная с .NET 4.0; вам придется развернуть свое собственное решение, которое, как вы обнаружили, не всегда легко сделать, если ваша инфраструктура предполагает однопоточность.
Я воспользуюсь этой возможностью, чтобы отметить, что L2S построен поверх ADO.NET, который сам полностью поддерживает асинхронную работу - лично я бы предпочел иметь дело непосредственно с этим нижним уровнем и писать сам SQL, просто чтобы убедитесь, что я полностью понял, что происходит по сети.
SQL Server Solution?
При этом я должен спросить - должно ли это быть решение на C #? Если вы можете составить свое решение из набора операторов вставки / обновления, вы можете просто отправить SQL напрямую, и ваши проблемы с потоками и производительностью исчезнут. * Мне кажется, что ваши проблемы связаны не с реальными преобразованиями данных, сделано, но сосредоточиться вокруг того, чтобы сделать их производительными из .NET. Если .NET удаляется из уравнения, ваша задача становится проще. В конце концов, лучшим решением часто является то, что вы пишете наименьшее количество кода, верно? ;)
Даже если ваша логика обновления / вставки не может быть выражена строго в реляционной форме, в SQL Server есть встроенный механизм для перебора записей и выполнения логики - хотя они справедливо порочны для многих случаев использования, курсоры могут фактически подходить для вашей задачи.
Если это задача, которая должна повторяться неоднократно, вы могли бы извлечь большую пользу из ее кодирования как хранимой процедуры.
* Конечно, длительно работающий SQL приносит свои собственные проблемы, такие как эскалация блокировки и использование индекса, с которыми вам придется бороться.
Решение C #
Конечно, может случиться так, что об этом в SQL не может быть и речи - может быть, решения вашего кода зависят, например, от данных, поступающих из других мест, или, возможно, ваш проект имеет строгое соглашение, запрещающее использование SQL , Вы упоминаете некоторые типичные ошибки многопоточности, но, не видя ваш код, я не могу помочь с ними конкретно.
Делать это из C #, очевидно, целесообразно, но вам нужно учитывать тот факт, что для каждого звонка, который вы делаете, будет существовать фиксированная величина задержки. Вы можете уменьшить влияние задержки в сети, используя пулы соединений, активируя несколько активных наборов результатов и используя асинхронные методы Begin / End для выполнения ваших запросов. Даже несмотря на все это, вам все равно придется признать, что отправка данных из SQL Server в ваше приложение обходится дорого.
Один из лучших способов не допустить, чтобы ваш код перешагивал через себя, - это избегать максимально возможного разделения изменяемых данных между потоками. Это будет означать, что нельзя использовать один и тот же DataContext в нескольких потоках. Следующим лучшим подходом является блокировка критических участков кода, которые касаются общих данных - lock
блокирует весь доступ к DataContext, от первого чтения до окончательной записи. Такой подход может полностью устранить преимущества многопоточности; Скорее всего, вы можете сделать свой замок более мелкозернистым, но будьте осторожны, это путь боли.
Гораздо лучше полностью отделить свои операции друг от друга. Если вы можете разделить свою логику между «основными» записями, это идеально, то есть, если нет связей между различными дочерними таблицами и если одна запись в «основной» не имеет последствий для Во-вторых, вы можете разделить свои операции на несколько потоков следующим образом:
private IList<int> GetMainIds()
{
using (var context = new MyDataContext())
return context.Main.Select(m => m.Id).ToList();
}
private void FixUpSingleRecord(int mainRecordId)
{
using (var localContext = new MyDataContext())
{
var main = localContext.Main.FirstOrDefault(m => m.Id == mainRecordId);
if (main == null)
return;
foreach (var childOneQuality in main.ChildOneQualities)
{
// If child one is not found, create it
// Create the relationship if needed
}
// Repeat for ChildTwo and ChildThree
localContext.SaveChanges();
}
}
public void FixUpMain()
{
var ids = GetMainIds();
foreach (var id in ids)
{
var localId = id; // Avoid closing over an iteration member
ThreadPool.QueueUserWorkItem(delegate { FixUpSingleRecord(id) });
}
}
Очевидно, что это такой же игрушечный пример, как и псевдокод в вашем вопросе, но, надеюсь, он заставляет задуматься о том, как распределить свои задачи так, чтобы между ними не было (или было бы минимальное) общее состояние.Это, я думаю, будет ключом к правильному решению на C #.
РЕДАКТИРОВАТЬ Отвечая на обновления и комментарии
Если вы столкнулись с проблемами согласованности данных, я бы посоветовал применять семантику транзакций -Вы можете сделать это с помощью System.Transactions.TransactionScope (добавьте ссылку на System.Transactions).С другой стороны, вы можете сделать это на уровне ADO.NET, получив доступ к внутреннему соединению и вызвав на нем BeginTransaction
(или как бы не вызывался метод DataConnection).
Вы также упоминаете взаимоблокировки.То, что вы боретесь с взаимоблокировками SQL Server, указывает на то, что реальные запросы SQL наступают друг другу на ноги.Не зная, что на самом деле отправляется по телеграфу, сложно сказать подробно, что происходит и как это исправить.Достаточно сказать, что взаимоблокировки SQL возникают в результате SQL-запросов, а не обязательно из потоковых конструкций C # - вам необходимо проверить, что именно происходит по проводам.Моя интуиция говорит мне, что если каждая «основная» запись действительно независима от других, тогда не должно быть необходимости в блокировке строк и таблиц, и что Linq to SQL, вероятно, является здесь виновником.
Выможет получить дамп необработанного SQL, генерируемого L2S в вашем коде, установив для свойства DataContext.Log
что-то, например, Console.Out.Хотя я никогда не использовал его лично, я понимаю, что LINQPad предлагает средства L2S, и вы, возможно, также сможете использовать SQL там.
SQL Server Management Studio предоставит вам остальную часть пути -используя Activity Monitor, вы можете следить за эскалацией блокировки в реальном времени.С помощью Query Analyzer вы можете получить представление о том, как SQL Server будет выполнять ваши запросы.С их помощью вы сможете получить четкое представление о том, что делает ваш код на стороне сервера, и, в свою очередь, о том, как его исправить.