Вам нужно добавить зависимость buildscript Firebase Gradle в build.gradle (уровень проекта)
classpath 'com.google.gms:google-services:3.1.0'
и добавить плагин Firebase для Gradle в приложении / build.gradle
apply plugin: 'com.google.gms.google-services'
build.gradle will include these new dependencies:
compile 'com.google.firebase:firebase-database:11.0.4'
Источник: Android Studio Assistant
Существует небольшая вероятность, что SomethingHappened
станет null
после нулевой проверки, но до вызова. Однако MulticastDelagate
s неизменяемы, поэтому, если вы сначала назначили переменную, нулевую проверку против переменной и вызовите ее, вы можете быть в безопасности от этого сценария (self plug: я написал сообщение в блоге об этом некоторое время назад).
Однако есть оборотная сторона монеты; если вы используете подход temp variable, ваш код защищен от NullReferenceException
s, но может случиться так, что событие вызовет прослушиватели событий после того, как они были отделены от события . Это всего лишь кое-что, с чем можно справиться самым изящным способом.
Чтобы обойти это, у меня есть метод расширения, который я иногда использую:
public static class EventHandlerExtensions
{
public static void SafeInvoke<T>(this EventHandler<T> evt, object sender, T e) where T : EventArgs
{
if (evt != null)
{
evt(sender, e);
}
}
}
Используя этот метод , вы можете вызвать такие события, как это:
protected void OnSomeEvent(EventArgs e)
{
SomeEvent.SafeInvoke(this, e);
}
Это зависит от того, что вы подразумеваете под потокобезопасностью. Если ваше определение включает в себя предотвращение NullReferenceException
, то первым примером является more safe. Однако, если вы переходите к более строгому определению, в котором обработчики событий должны быть вызываться, если они существуют, то ни один из них не является безопасным. Причина связана с сложностями модели памяти и барьеров. Может быть, на самом деле есть обработчики событий, привязанные к делегату, но поток всегда читает ссылку как null. Правильный способ исправить это - создать явный барьер памяти в точке, где ссылка делегата будет записана в локальную переменную.
lock
(или любой механизм синхронизации). volatile
для переменной события. Thread.MemoryBarrier
. Несмотря на проблему неудобного обзора, которая мешает вам выполнять однострочный инициализатор, я по-прежнему предпочитаю метод lock
.
protected virtual void OnSomethingHappened(EventArgs e)
{
EventHandler handler;
lock (this)
{
handler = SomethingHappened;
}
if (handler != null)
{
handler(this, e);
}
}
важно отметить, что в данном конкретном случае проблема барьер памяти, вероятно, спорный вопрос, потому что маловероятно, что читает переменные будут сняты вне вызовов методов. Но нет гарантии, особенно если компилятор решает встроить метод.
Я попытался высушить ответ Джесси С. Слайсер с помощью:
public class ThreadSafeEventDispatcher<T> where T : class
{
readonly object _lock = new object();
private class RemovableDelegate
{
public readonly T Delegate;
public bool RemovedDuringRaise;
public RemovableDelegate(T @delegate)
{
Delegate = @delegate;
}
};
List<RemovableDelegate> _delegates = new List<RemovableDelegate>();
Int32 _raisers; // indicate whether the event is being raised
// Raises the Event
public void Raise(Func<T, bool> raiser)
{
try
{
List<RemovableDelegate> raisingDelegates;
lock (_lock)
{
raisingDelegates = new List<RemovableDelegate>(_delegates);
_raisers++;
}
foreach (RemovableDelegate d in raisingDelegates)
{
lock (_lock)
if (d.RemovedDuringRaise)
continue;
raiser(d.Delegate); // Could use return value here to stop.
}
}
finally
{
lock (_lock)
_raisers--;
}
}
// Override + so that += works like events.
// Adds are not recognized for any event currently being raised.
//
public static ThreadSafeEventDispatcher<T> operator +(ThreadSafeEventDispatcher<T> tsd, T @delegate)
{
lock (tsd._lock)
if (!tsd._delegates.Any(d => d.Delegate == @delegate))
tsd._delegates.Add(new RemovableDelegate(@delegate));
return tsd;
}
// Override - so that -= works like events.
// Removes are recongized immediately, even for any event current being raised.
//
public static ThreadSafeEventDispatcher<T> operator -(ThreadSafeEventDispatcher<T> tsd, T @delegate)
{
lock (tsd._lock)
{
int index = tsd._delegates
.FindIndex(h => h.Delegate == @delegate);
if (index >= 0)
{
if (tsd._raisers > 0)
tsd._delegates[index].RemovedDuringRaise = true; // let raiser know its gone
tsd._delegates.RemoveAt(index); // okay to remove, raiser has a list copy
}
}
return tsd;
}
}
Использование:
class SomeClass
{
// Define an event including signature
public ThreadSafeEventDispatcher<Func<SomeClass, bool>> OnSomeEvent =
new ThreadSafeEventDispatcher<Func<SomeClass, bool>>();
void SomeMethod()
{
OnSomeEvent += HandleEvent; // subscribe
OnSomeEvent.Raise(e => e(this)); // raise
}
public bool HandleEvent(SomeClass someClass)
{
return true;
}
}
Любые серьезные проблемы с этим подходом?
Код был только ненадолго протестирован и отредактирован немного на вставке. Подтвердите, что List & lt;> не отличный выбор, если много элементов.
На самом деле нет, второй пример не считается потокобезопасным. Событие SomethingHappened может вычисляться с ненулевым значением в условном выражении, затем при вызове должно быть null. Это классическое состояние гонки.
Я поддерживаю этот фрагмент как ссылку для безопасного многопоточного доступа к событиям для установки и стрельбы:
/// <summary>
/// Lock for SomeEvent delegate access.
/// </summary>
private readonly object someEventLock = new object();
/// <summary>
/// Delegate variable backing the SomeEvent event.
/// </summary>
private EventHandler<EventArgs> someEvent;
/// <summary>
/// Description for the event.
/// </summary>
public event EventHandler<EventArgs> SomeEvent
{
add
{
lock (this.someEventLock)
{
this.someEvent += value;
}
}
remove
{
lock (this.someEventLock)
{
this.someEvent -= value;
}
}
}
/// <summary>
/// Raises the OnSomeEvent event.
/// </summary>
public void RaiseEvent()
{
this.OnSomeEvent(EventArgs.Empty);
}
/// <summary>
/// Raises the SomeEvent event.
/// </summary>
/// <param name="e">The event arguments.</param>
protected virtual void OnSomeEvent(EventArgs e)
{
EventHandler<EventArgs> handler;
lock (this.someEventLock)
{
handler = this.someEvent;
}
if (handler != null)
{
handler(this, e);
}
}
add
и remove
, вероятно, не нужно, поскольку компилятор испускает блокировки в автоматических реализациях. Хотя, я думал, что я помню, что что-то изменилось с этим в .NET 4.0.
– Brian Gideon
8 September 2010 в 17:21
this
, а это означает, что код, внешний для класса, может привести к запутыванию механизма путем блокировки экземпляра. Джон Скит представил вдохновение здесь csharpindepth.com/Articles/Chapter2/Events.aspx#threading .
– Jesse C. Slicer
8 September 2010 в 17:42
this
. У кого есть быстрая ссылка на изменения в .NET 4.0? Если нет, я просто подниму спецификацию.
– Brian Gideon
8 September 2010 в 18:43
Объявите ваше событие как это, чтобы получить безопасность потоков:
public event EventHandler<MyEventArgs> SomethingHappened = delegate{};
И вызывать его следующим образом:
protected virtual void OnSomethingHappened(MyEventArgs e)
{
SomethingHappened(this, e);
}
Хотя этот метод больше не нужен.
Начиная с C # 6.0 вы можете использовать монодичный оператор Null-conditional ?.
, чтобы проверять события с нулевым и повышающим потоком простым и потокобезопасным способом.
SomethingHappened?.Invoke(this, args);
Это поточно-безопасный, поскольку он оценивает левая сторона только один раз и сохраняет ее во временной переменной. Вы можете прочитать здесь здесь в разделе «Операторы с нулевым условием».
Обновление: на самом деле обновление 2 для Visual Studio 2015 теперь содержит рефакторинг для упрощения делегирования делегатов, тип обозначений. Вы можете прочитать об этом в этом объявлении .
null
ситуация с await
, но вы всегда можете комбинировать ее с ??
operator likethis: await (foo?.DoAsync() ?? Task.CompletedTask);
– Krzysztof Branicki
14 August 2018 в 11:00
Чтобы любой из этих потоков был потокобезопасным, вы предполагаете, что все объекты, которые подписываются на событие, также являются потокобезопасными.
На самом деле, первый является потокобезопасным, а второй - нет. Проблема со вторым заключается в том, что делегат SomethingHappened может быть изменен на null между нулевой верификацией и вызовом. Для более полного объяснения см. http://blogs.msdn.com/b/ericlippert/archive/2009/04/29/events-and-races.aspx .
Для .NET 4.5 лучше использовать Volatile.Read
для назначения временной переменной.
protected virtual void OnSomethingHappened(EventArgs e)
{
EventHandler handler = Volatile.Read(ref SomethingHappened);
if (handler != null)
{
handler(this, e);
}
}
Обновление:
Это объясняется в этой статье: http: //msdn.microsoft.com/en-us/magazine/jj883956.aspx. Кроме того, это было объяснено в четвертом издании «CLR via C #».
Основная идея заключается в том, что JIT-компилятор может оптимизировать ваш код и удалить локальную временную переменную. Итак, этот код:
protected virtual void OnSomethingHappened(EventArgs e)
{
EventHandler handler = SomethingHappened;
if (handler != null)
{
handler(this, e);
}
}
будет скомпилирован в это:
protected virtual void OnSomethingHappened(EventArgs e)
{
if (SomethingHappened != null)
{
SomethingHappened(this, e);
}
}
Это происходит в определенных особых обстоятельствах, однако это может произойти.
There is a back side of the coin though; if you use the temp variable approach (...) it could be that the event will invoke event listeners after they have been detached from the event
: это всегда возможность; это неизбежно. – ANeves 20 February 2014 в 13:57SomeEvent?.Invoke(sender, args);
– Tim Long 10 November 2015 в 11:20