.NET Framework: привязаны ли объекты к квартире? - PullRequest
7 голосов
/ 20 ноября 2008

Дано: Создание объекта подключения ADO из одного потока и передача его другому потоку запрещено . Эти две темы - разные квартиры, и, хотя первая нить никогда не коснется ее (1007 * никогда * 1008) (даже не сохранит ссылку на нее!), Это не имеет значения.

То, что объект ADO Connection был создан ThreadA, ThreadA - единственный поток, когда-либо, при любых обстоятельствах, вообще, когда-либо, которому разрешено использовать этот объект Connection, когда-либо.

Теперь замените «ADO Connection» на «ADO.NET Connection». Применяется ли то же правило?


Я знаю, что большинство объектов в .NET Framework не являются поточно-ориентированными. Например, структура DictionaryEntry в SDK гласит:

Резьба безопасности
Любые открытые статические члены этот тип потокобезопасен. любой членам экземпляров не гарантируется быть в безопасности потока.

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

lock (myObject)
{
   ...
}

Но есть нечто большее, чем не поточнобезопасность.

В COM (некоторые) объекты связаны с " квартирой ", которая его создала. После того, как объект был построен в одной квартире, вам запрещено получать доступ к нему из другой квартиры - независимо от того, насколько вы защищаете этот объект от одновременного множественного доступа.

Существует ли похожая концепция в .NET?


Дополнительная информация

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

Резьба безопасности

Только следующие члены являются темами Сейф: BeginInvoke, EndInvoke, Invoke, InvokeRequired и CreateGraphics, если ручка для управления уже был создан. Вызов CreateGraphics до того, как ручка управления была созданный на фоновом потоке может вызвать недопустимые перекрестные вызовы.

Нет упоминаний о том, что Controls выдает исключения, когда вы создаете и используете их из одного потока - когда этот поток не является первым потоком, который был создан при запуске приложения.

Но как насчет произвольных объектов? Как насчет:

public class MyClass
{
    int _number;

    public int Number { get { return _number; } set { _number = value; } }
}

MyClass myObject = new MyClass();

Пока я синхронизирую доступ к myObject, двум потокам разрешено общаться с ним?


То же самое касается:

List<Object> sharedList = new List<Object>();

Два потока могут общаться со списком, если они не делают это одновременно, обычно с:

lock (sharedList)
{
    sharedList.Add(data);
}

разрешено ли двум потокам касаться одного и того же объекта?


То же самое касается:

IAsyncResult ar = BeginSetLabelToTheValueINeed(label1);
...
EndSetLabelToTheValueINeed(ar);

То же самое касается:

//Fetch image on connection that is an existing DB transaction
public static Bitmap GetImageThumbnail(DbConnection conn, int imageID) 
{
}

преобразуется в шаблон асинхронного делегата:

//Begin fetching an image on connection that is an existing DB transaction
IAsyncResult ar = BeginGetImageThumbnuts(conn, imageID, callback, stateOjbect);
...
//Finish fetching an image on connection that is an existing DB transaction
Bitmap thumb = EndGetImageNumbthail(ar);

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

Ответы [ 7 ]

4 голосов
/ 27 января 2009

.NET объекты не привязаны к квартире. Квартиры действительно вступают в игру только тогда, когда ваш .NET-код взаимодействует с COM-объектами, хотя это не сложно сделать по незнанию. Например, если вы взаимодействуете с буфером обмена Windows, вы активируете подсистему COM. Квартиры устанавливаются на уровне потока, и как только ваша нить установит свою квартиру (MTA, STA и т. Д.), Она не может измениться, поэтому очень важно правильно подобрать квартиру. Все приложения WinForms имеют [STAThread], присоединенный к их методу Main (), чтобы гарантировать, что основным потоком приложения (т. Е. Потоком передачи сообщений GUI) является STA и он может взаимодействовать с такими удобными средствами, как буфер обмена Windows.

Среда выполнения .NET должна знать о подсистеме COM, чтобы обеспечить функциональность взаимодействия COM. Впрочем, довольно редко нужны внутренние ужасные детали.

Из того, что я понимаю, одна из целей разделения COM-объектов на разные квартиры состояла в том, чтобы приложения могли использовать программные компоненты, разработанные с различными моделями потоков, без необходимости самим заниматься такими проблемами.

Здесь возникает много сложных проблем, в частности, идея о том, что однопоточные библиотеки часто не создаются для того, чтобы справляться с повторным входом, и, конечно, не поддерживают идею наличия двух потоков выполнения, изменяющих объект одновременно.

Например, предположим, что вы хотите использовать COM-объект в вашем новом многопоточном приложении C ++. Этот COM-объект был разработан в Visual Basic и не является потокобезопасным, но вы хотите использовать несколько экземпляров этого компонента из нескольких потоков. Хотя верно и то, что вы могли бы добавить некоторый механизм блокировки для предотвращения неправильного доступа, тот факт, что объект существует в его собственной однопотоковой квартире (STA), означает, что подсистема COM будет сериализовать доступ к объекту от вашего имени. Вам не нужно знать модель потоков COM-объекта, чтобы использовать его, или, по крайней мере, так он обычно работает.

.NET был написан для нашего нового многопоточного мира и поэтому не использует понятия квартир. Среда выполнения определенно знает о них и должна поддерживать взаимодействие с устаревшими COM-объектами.

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

Если вам нужна действительно сложная информация, вы можете проверить Квартиры и насосы в CLR .

3 голосов
/ 27 января 2009

СЪЕМКА за награду!

Хорошо, некоторые классы / каркасы классов в .NET имеют методы, привязанные к квартире, но ТОЛЬКО ПО ДИЗАЙНУ. Это означает, что вы должны указать код, чтобы сделать это. Это не установлено по умолчанию. Кодирование для этого - своего рода хлам. Вы должны получить идентификатор нити, который хотите использовать, и проверять его все время. Я не думаю, что в инфраструктуре есть какие-либо средства, облегчающие это, кроме тех, которые предназначены для компонентов пользовательского интерфейса, отслеживающих поток пользовательского интерфейса. Эта структура поддержки, вероятно, находится глубоко в стеке пользовательского интерфейса; Я никогда не видел его ни в одной документации MSDN.

Что более распространено, так это настройка потока квартиры. Многие части всей структуры ограничены конструкцией потоков STA ( STAThreadAttribute ). Обычно это происходит потому, что в глубине своей сути они касаются COM-объектов, которые часто требуют, чтобы они использовались только в потоках STA. Например, основной поток в приложениях winforms отмечен атрибутом STAThreadAttribute, так что каждый компонент пользовательского интерфейса может быть затронут только в том же потоке, в котором он создан, - в потоке пользовательского интерфейса. Интересно, что XamlReader в WPF знает о текущем пакете потоков и даже не десериализует компоненты пользовательского интерфейса, если поток, в котором он выполняется, не является потоком STA.

"Имеет ли стандартное POCO сходство с потоком или заботу о том, находится ли он в потоке STA или MTA?" Ответ отрицательный, если только вы не закодируете его так, или не пометите его методы с помощью STAThreadAttribute или если объект будет создан в стеке вызовов, который где-то в своей иерархии имеет метод, помеченный STAThreadAttribute .

"Имеет ли объект ADO.NET сходство потоков, и он осведомлен о квартире?"

Ну, DbConnection расширяется от Component, который расширяет MarshalByRefObject. Компонент MBRO, DbConnection и его потомки (т. Е. SqlConnection) не содержат методов, помеченных с помощью атрибута STAThreadAttribute. Быстрая проверка их основных зависимостей (таких как DbTransaction) не обнаруживает, что какие-либо из методов этих классов также помечены как STA. Поэтому они не осведомлены о квартире.

Быстрый просмотр с помощью Reflector не находит очевидного просмотра идентификатора потока. DbConnection действительно зависит от транзакции, которая кажется наиболее вероятным средством поиска потоков.

Я отключил Reflector и проверил некоторые из этих классов.
DbConnection - не заботится об идентификаторе текущего потока
SqlConnection - выполняет отладку некоторых потоков при отладке
SqlTransaction - имеет функцию под названием "ZombieCheck", но не наблюдает за потоком
DbTransaction - здесь ничего нет.

Так что я думаю, можно с уверенностью сказать, что объекты ADO.NET НЕ имеют привязки к потокам. Я даже не видел, чтобы они использовали Thread.Begin / EndThreadAffinity .


EDIT

Как указано в комментариях Джереми, я продолжал говорить о том, что КЛАССЫ помечаются атрибутом, а не МЕТОДОМ КЛАССОВ. ОГРОМНАЯ ошибка. Я облажался, создав впечатление, что я смотрю только на определения классов, а не на весь класс.

Я отредактировал это, чтобы прояснить, и позвольте мне сказать это здесь, чтобы быть абсолютно ясным: ни один метод класса в System.Data не помечен как STAThreadAttribute. Вы можете проверить это сами с помощью следующего хакерского кода:

SqlConnection sc = new SqlConnection();
var data = AppDomain.CurrentDomain.GetAssemblies()
           .Where(x => x.FullName.StartsWith("System.Data,")).First();
var dtypes = data.GetTypes();
var results = new List<string>();
foreach (var type in dtypes)
    foreach (var method in type.GetMethods())
        foreach (var attr in 
                method.GetCustomAttributes(true).OfType<System.Attribute>())
        {
            results.Add(string.Format("{0} {1} {2}", 
                               type.Name, method.Name, attr.GetType().Name));
            if (attr.GetType() == typeof(STAThreadAttribute)) 
                                           throw new Exception("SHIII");
        }

Кроме того, классы пользовательского интерфейса в winforms также не используют STAThreadAttribute; это приложение, в котором используется этот атрибут (как опять же указал Джереми).

Есть особый случай, когда я сталкиваюсь с классом, будучи осведомленным о квартире. XamlReader, который десериализует xaml и создает классы пользовательского интерфейса WPF, сгенерирует исключение, когда его метод Load вызывается в потоке MTA. Это даже не в документах, что раздражает ... Так что, опять же, классы не знают квартиры по умолчанию; Вы должны кодировать его таким образом, или вы должны информировать их экземпляры, обновляя их в цепочке методов, содержащей метод, помеченный атрибутом STAThreadAttribute. Уф. Это сложнее объяснить, чем понять ...

1 голос
/ 27 января 2009

Объекты в .NET не привязаны к квартире, если вы специально не сделаете их таковыми.

Например, если вы используете локальное хранилище потоков, у вас будут проблемы с несколькими потоками, обращающимися к объекту, если вы предполагаете, что весь доступ к объекту будет происходить в одном потоке.

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

Что касается других ваших дел, давайте рассмотрим их по одному с комментариями:


А как насчет произвольных объектов? Как насчет:

public class MyClass
{
    int _number;

    public int Number { get { return _number; } set { _number = value; } }
}

MyClass myObject = new MyClass();

Пока я синхронизирую доступ к myObject, двум потокам разрешено общаться с ним?

Ответ : Да, и в этом случае необходимость синхронизации зависит от ваших требований.

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

Однако сохранение значения для int является атомарной операцией. Блокировка необходима для получения барьера памяти, чтобы при чтении значения во втором потоке не использовалась кэшированная копия.

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

Для более сложного объекта, скажем, установка структуры, превышающей собственный размер шины процессора (т. Е. Больше 32 или 64-битной в зависимости от), требуется блокировка, так как данные копируются в нужное место в памяти не атомарная операция в таком случае. Другими словами, без блокировки вы рискуете прочитать половину старых, наполовину новых данных, если один поток попытается прочитать данные в середине операции записи, выполняющейся в другом потоке.


То же самое касается:

List<Object> sharedList = new List<Object>();

Два потока могут общаться со списком, если они не делают это одновременно, обычно с:

lock (sharedList)
{
    sharedList.Add(data);
}

разрешено ли двум потокам касаться одного и того же объекта?

Ответ : Да. Обычно рекомендуется использовать свойство SyncRoot интерфейса ICollection в коллекциях или просто использовать другой объект блокировки.


То же самое касается:

IAsyncResult ar = BeginSetLabelToTheValueINeed(label1);
...
EndSetLabelToTheValueINeed(ar);

Ответ : В этом я не уверен, какая именно здесь проблемная переменная? Если label1, то здесь нет ничего, что мешало бы нескольким потокам обращаться к этой переменной и связываться с ней, и вам необходимо использовать механизмы блокировки для предотвращения таких проблем.


То же самое касается:

//Fetch image on connection that is an existing DB transaction
public static Bitmap GetImageThumbnail(DbConnection conn, int imageID) 
{
}

преобразуется в шаблон асинхронного делегата:

//Begin fetching an image on connection that is an existing DB transaction
IAsyncResult ar = BeginGetImageThumbnuts(conn, imageID, callback, stateOjbect);
...
//Finish fetching an image on connection that is an existing DB transaction
Bitmap thumb = EndGetImageNumbthail(ar);

Ответ : Если под этим вы подразумеваете, что будете выполнять несколько таких запросов параллельно, вы, вероятно, не получите много, так как вам нужно будет сериализовать доступ к объекту соединения, так как они не ' Это безопасно. Возможно, вам лучше сделать метод миниатюр открытым и закрыть свое собственное соединение.

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


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

0 голосов
/ 27 января 2009

Смотрите эту удобную страницу от Алика Левина

http://blogs.msdn.com/alikl/archive/2008/06/20/pitfalls-with-net-multithreading-and-com-objects-threads-must-have-compatible-apartment-models-mta-vs-sta.aspx

.net Объект моделируется MTA. и это только скрыто, если вы взаимодействуете с COM-объектами

Подводные камни с .Net Многопоточность и COM-объекты - должны быть темы Совместимые модели квартир (MTA vs. STA)

Будьте внимательны при реализации многопоточность .Net в сочетании с COM-объектами. Нить квартиры Модели имеют значение.

.Net темы имеют многопоточный Модель квартиры (MTA) по умолчанию. COM объекты имеют однониточную квартиру (СТО). Вызов COM-объектов в .Net темы, которые вы порождаете, могут вызвать непредсказуемый результат.

Многопоточность в .Net легко реализовано на основе либо потока, либо Объекты ThreadPool. Thread.start () метод порождает новую тему, которая имеет Модель многопоточных квартир (MTA). ThreadPool.QueueUserWorkItem (MyMethod) очереди myMethod для выполнения доступный поток управляется им.

0 голосов
/ 20 ноября 2008

Есть только несколько вариантов использования, о которых я знаю, желая перечисленного выше:

  • Работа с SqlCe как трюк с производительностью
  • Работа с NHibernate и поддержание связи в течение всего времени (хотя это в основном противоречит лучшей практике NHibernate)

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

Я согласен с StingyJack, в большинстве случаев ваши объекты ADO должны появляться внутри использования (создаваться и уничтожаться в рамках) и быть локальными переменными, тем самым устраняя проблемы с потоками.

Сделка с WinForm связана с разницей между сообщениями POST и SEND (отправка сообщений блокируется потребителю до их завершения). В действительности это не исключение, исключение - это просто предупреждение, добавленное в 2.00 при отладке. На практике без исключения это приводит к (казалось бы) случайным блокировкам.

0 голосов
/ 20 ноября 2008

Во-первых, вам не требуется для синхронизации доступа к объектам в многопоточных сценариях. Вы можете свободно «поиграть» с любыми членами ваших собственных классов из нескольких потоков без каких-либо ограничений со стороны CLR. Тот факт, что Форма (и элементы управления в целом) не позволяют вам вызывать элементы в другом потоке, является подробностью реализации (и той, которая не была явной до .Net 2.0).

При этом, как правило, необходимо , чтобы принудительно установить некоторый тип синхронизации для выполнения чего-либо полезного с объектами, доступными для нескольких потоков.

На ваш конкретный вопрос. Я считаю, что SqlConnection можно использовать из нескольких потоков, но не одновременно. Невозможность одновременного использования SqlConnection практически завершена сводит на нет любые преимущества в производительности, которые вы можете получить, используя общее соединение. Учитывая, как дешево создать новое SqlConnection (особенно после объединения в пул), бессмысленно повторное использование соединения следующим образом.

0 голосов
/ 20 ноября 2008

Почему вы хотите создать соединение в одном потоке и использовать его из другого?

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

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