Является ли `System.MulticastDelegate` потокобезопасным?
Я ищу кого-то, кто мог бы знать больше об этом, моя интуиция говорит мне, что ответ "нет, это не потокобезопасно", но я хочу быть уверенным.
Чтобы проиллюстрировать мой вопрос, я предоставил некоторый контекст с этим классом
public class MyContext
{
private readonly object _lock = new object();
public delegate bool MyDelegate(MyContext context);
private MyDelegate _multicastDelegate;
public MyContext()
{
_multicastDelegate = null;
}
public void AddDelegate(MyDelegate del)
{
lock(_lock)
{
_multicastDelegate += del;
}
}
public void RemoveDelegate(MyDelegate del)
{
lock(_lock)
{
_multicastDelegate += del;
}
}
public void Go()
{
_multicastDelegate.Invoke(this);
}
}
Изменить: я добавил блокировки в приведенном выше примере, но это действительно не вопрос моего вопроса.
Я пытаюсь лучше понять, является ли массив, содержащий список вызовов, потокобезопасным. Честно говоря, я не совсем понимаю, как все это складывается воедино, и была бы признательна за помощь.
Согласно документации, которую я нашел, единственная цитата, которая не дает реального понимания, заключается в следующем:
MulticastDelegate имеет связанный список делегатов, называемый списком вызовов, состоящим из одного или нескольких элементов. Когда многоадресный делегат вызывается, делегаты в списке вызовов вызываются синхронно в порядке их появления. Если во время выполнения списка возникает ошибка, возникает исключение.
https://msdn.microsoft.com/en-us/library/system.multicastdelegate.aspx
Заранее спасибо.
2 ответа
Делегаты неизменны. Вы никогда не меняете делегата. Любой метод, который, по-видимому, изменяет делегат, фактически создает новый экземпляр.
Делегаты неизменны; После создания список вызовов делегата не изменяется.
Таким образом, нет оснований для беспокойства о том, что список вызовов может быть обновлен во время вызова делегата.
Однако, что вы должны остерегаться, и чего вы не смогли сделать в вашем методе, так это когда делегат может быть null
,
(new MyContext()).Go();
вызовет исключение. Раньше вы должны были защититься от этого, считывая значение в локальную переменную, проверяя его на null и затем вызывая его. Теперь это может быть легко решено как:
public void Go()
{
_multicastDelegate?.Invoke(this);
}
Определение потоковой безопасности, используемое в документации MSDN, означает код, свойство которого синхронизировано. Обычно в нем не указывается, что именно синхронизируется, но это может быть тип для статических членов и сам экземпляр для членов экземпляра, или это может быть некоторый внутренний объект, такой как SyncRoot
во многих типах коллекций.
Хотя делегаты являются неизменяемыми, вы все равно должны синхронизироваться правильно. .NET и C#, в отличие от Java, не гарантируют безопасную публикацию, поэтому, если вы не гарантируете синхронизацию, вы можете наблюдать частично инициализированный объект в других потоках1.
Чтобы сделать ваш код потокобезопасным, вам просто нужно использовать _lock
при чтении из поля делегата, но вы можете позвонить Invoke
вне замка, посадка на делегата ответственности, чтобы сохранить свою собственную безопасность потока.
public class MyContext
{
private readonly object _lock = new object();
public delegate bool MyDelegate(MyContext context);
private MyDelegate _delegate;
public MyContext()
{
}
public void AddDelegate(MyDelegate del)
{
lock (_lock)
{
_delegate += del;
}
}
public void RemoveDelegate(MyDelegate del)
{
lock (_lock)
{
// You had a bug here, +=
_delegate -= del;
}
}
public void Go()
{
MyDelegate currentDelegate;
lock (_lock)
{
currentDelegate = _delegate;
}
currentDelegate?.Invoke(this);
}
}
- Внедрение Microsoft.NET Framework всегда делает нестабильные записи (или, как они говорят), что неявно дает вам безопасную публикацию, но я лично не полагаюсь на это.