Синхронизация данных для многопоточности
Если несколько потоков могут вызывать свойства и методы отдельного объекта, эти вызовы важно синхронизировать. В противном случае поток может прерваться действиями другого потока, а объект может остаться в недопустимом состоянии. Класс, члены которого защищены от подобных прерываний, называется потокобезопасным.
.NET предоставляет несколько способов синхронизации доступа к экземпляру и статическим членам:
Синхронизированные области кода. Используя класс Monitor или поддержку компилятора для этого класса, можно синхронизировать только тот блок кода, которому эту требуется, повысив тем самым производительность.
Синхронизация вручную. Вы можете использовать объекты синхронизации, предоставляемые библиотекой классов .NET. См. статью Обзор примитивов синхронизации, в которой обсуждается класс Monitor.
Синхронизированные контексты. Для приложений .NET Framework и Xamarin вы можете использовать SynchronizationAttribute для реализации простой автоматической синхронизации объектов ContextBoundObject.
Классы коллекции в пространстве имен System.Collections.Concurrent. Эти классы предоставляют встроенные синхронизированные операции добавления и удаления. Дополнительные сведения см. в разделе Потокобезопасные коллекции.
CLR предоставляет потоковую модель, где классы делятся на ряд категорий, которые можно синхронизировать различными способами в зависимости от конкретных требований. В следующей таблице показано, какая поддержка синхронизации предоставляется для полей и методов в соответствующей категории синхронизации.
Категория | Глобальные поля | Статические поля | Статические методы | Поля экземпляров | Методы экземпляра | Определенные блоки кода |
---|---|---|---|---|---|---|
Синхронизации нет | No | No | No | No | No | No |
Синхронизированные контексты | No | No | No | Да | Да | Нет |
Синхронизированные области кода | No | No | Только если помечены | No | Только если помечены | Только если помечены |
Синхронизация вручную | Руководство | Руководство | Руководство | Руководство | Руководство | Руководство |
Без синхронизации
По умолчанию для объектов. Любой поток может получить доступ к любому методу или полю в любое время. Одновременно обращаться к этим объектам может только один поток.
Ручная синхронизация
Библиотека классов .NET предоставляет ряд классов для синхронизации потоков. См. раздел Обзор примитивов синхронизации.
Синхронизированные области кода
Класс Monitor или ключевое слово компилятора можно использовать для синхронизации блоков кода, методов экземпляров и статических методов. Синхронизированные статические поля не поддерживаются.
Visual Basic и C# поддерживают маркировку блоков кода ключевым словом определенного языка, оператором lock
в C# или SyncLock
в Visual Basic. Если код выполняется потоком, он пытается получить блокировку. Если блокировка уже получена другим потоком, поток блокируется, пока блокировка не станет доступной. Когда поток выходит из синхронизированного блока кода, блокировка снимается независимо от того, каким образом поток выходит из блока.
Примечание.
Начиная с C# 13, lock
инструкция распознает, является ли заблокированный объект экземпляром System.Threading.Lock и использует EnterScope
метод для создания синхронизированного региона. Значение lock
, когда целевой объект не является экземпляром Lock
, и SyncLock
операторы реализуются с помощью Monitor.Enter и Monitor.Exitпоэтому другие методы Monitor могут использоваться в сочетании с ними в синхронизированном регионе.
Метод можно также оформить с помощью MethodImplAttribute со значением MethodImplOptions.Synchronized, который действует точно так же, как Monitor или одно из ключевых слов компилятора, блокируя все тело метода.
С помощью Thread.Interrupt можно вывести поток из операций блокировки, таких как ожидание доступа к синхронизированной области кода. Thread.Interrupt также используется для вывода потоков из таких операций, как Thread.Sleep.
Внимание
Не блокируйте тип, т. е. typeof(MyType)
в C#, GetType(MyType)
в Visual Basic или MyType::typeid
в C++, для защиты методов static
(методы Shared
в Visual Basic). Вместо этого используйте закрытый статический объект. Кроме того, не блокируйте методы экземпляра, используя this
в C# (Me
в Visual Basic). Вместо этого используйте закрытый объект. Класс или экземпляр может заблокировать чужой код, вызвав при этом взаимоблокировку или проблемы производительности.
Поддержка компилятора
Visual Basic и C# поддерживают ключевое слово языка для блокировки объекта с помощью Monitor.Enter и Monitor.Exit. Visual Basic поддерживает оператор SyncLock, а C# поддерживает оператор lock.
В обоих случаях, если в коде блока возникает исключение, блокировка, введенная оператором lock или SyncLock, автоматически снимается. Компиляторы C# и Visual Basic выдают блок try/finally с Monitor.Enter в начале оператора try и Monitor.Exit в блоке finally. Если исключение возникает в блоке lock или SyncLock, запускается обработчик finally, позволяющий выполнить очистку.
Синхронизированные контексты
Только в приложениях .NET Framework и Xamarin можно использовать SynchronizationAttribute на любом ContextBoundObject для синхронизации всех методов и полей экземпляра. Блокировка распространяется на все объекты с одним и тем же контекстным доменом. К методам и полям могут обращаться сразу несколько потоков, но только по одному за раз.