На данный момент у меня есть несколько функций, которые выглядят так:
private bool inFunction1 = false;
public void function1()
{
if (inFunction1) return;
inFunction1 = true;
// do stuff which might cause function1 to get called
...
inFunction1 = false;
}
Я бы хотел объявить их так:
[NoReEntry]
public void function1()
{
// do stuff which might cause function1 to get called
...
}
Есть ли атрибут, который я могу добавить к функции, чтобы предотвратить повторный вход? Если нет, как бы я его сделал? Я слышал об атрибутах АОП, которые можно использовать для добавления кода до и после вызовов функций; подойдут ли они?
Предопределенного такого атрибута нет. Вы можете создавать новые атрибуты, но это вам не поможет. Проблема в том, что настраиваемый атрибут предотвращает повторный вызов метода, что я не думаю, что это выполнимо.
Оператор блокировки - это не то, что вам нужно, поскольку это приведет к блокировке вызовов и ожиданию, а не к немедленному возврату.
PS: используйте блок try ... finally в приведенном выше примере. В противном случае, если в середине функции возникнет исключение, inFunction1 останется истинным, и все вызовы вернутся немедленно.
Например :
if (inFunction1)
return;
try
{
inFunction1 = true;
// do stuff which might cause function1 to get called
...
}
finally
{
inFunction1 = false;
}
Я не думаю, что это будет возможно.
Ближайшим будет атрибут «Синхронизировано», но он заблокирует все последующие вызовы.
Вы можете обнаружить, что для этого можно использовать PostSharp - вместе с предложениями Энтони об использовании try / finally. Хотя, скорее всего, это будет неаккуратно. Также подумайте, хотите ли вы, чтобы повторный вход был для каждого потока или для каждого экземпляра. (Могут ли несколько потоков вызывать метод для начала или нет?)
В самом фреймворке ничего подобного нет.
Если это сделано для безопасности потоков, будьте осторожны с этой переменной.
Возможно, другой поток может войти в функцию и пройти проверку до того, как первый поток установит переменную.
Убедитесь, что он помечен как изменчивый:
private volatile bool inFunction1 = false;
"volatile" не исправит это состояние гонки.
Без переписывания сборки и IL вы не сможете создать настраиваемый атрибут, который изменяет код описанным вами способом.
Я предлагаю вместо этого использовать подход, основанный на делегатах, например для функций с одним аргументом:
static Func<TArg,T> WrapAgainstReentry<TArg,T>(Func<TArg,T> code, Func<TArg,T> onReentry)
{
bool entered = false;
return x =>
{
if (entered)
return onReentry(x);
entered = true;
try
{
return code(x);
}
finally
{
entered = false;
}
};
}
Этот метод принимает функцию для переноса (при условии, что она соответствует Func <TArg, T> - вы можете написать другие варианты или полностью универсальную версию с дополнительными усилиями) и альтернативную функцию для вызова в случае повторного входа. (Альтернативная функция может генерировать исключение или немедленно возвращать и т. д.) Затем во всем коде, где вы обычно вызываете переданный метод, вместо этого вы вызываете делегат, возвращаемый WrapAgainstReentry ().
Это по-прежнему вызывает проблемы, присущие многопоточным средам. См. Мой пост о летучих переменных выше.
Конечно, Роб. Это просто демонстрация, потому что она также обрабатывает только очень специфическую сигнатуру функции.
Вместо того, чтобы использовать bool и устанавливать его напрямую, попробуйте использовать long и Interlocked class:
long m_InFunction=0;
if (Interlocked.CompareExchange(ref m_InFunction,1,0)==0)
{
// We're not in the function
try
{
}
finally
{
m_InFunction=0;
}
}
else
{
// We're already in the function
}
Это сделает проверку потокобезопасной.
Из любопытства, есть ли конкретная причина использовать здесь long, а не int? (Тоже есть перегрузка CompareExchange(ref int,int,int))
Нет особой причины. Я много занимаюсь программированием на Win32, и связанные с этим функции занимают много времени. Использование int не повлияет на приведенный выше пример.
Вы знаете, что в C# long определяется как int64, верно?
Ваш Win32 api определен так, чтобы принимать int32, если вы используете C++ для таргетинга Win32, как int, так и long определены как int32. И ... ладно, тебе все равно.
Я предполагаю, что m_InFunction - это общая переменная среди потоков (иначе, в чем смысл решения: P) - с учетом имени, поля члена класса и CompareExchange покрывает вход в защищенную область, но я ошибаюсь в предполагая, что Interlocked.Exchange(ref m_InFunction, 0), вместо m_InFunction=0 в блоке finally необходимо на выходе, чтобы гарантировать, что другие потоки подхватят новое значение?
Вы можете создать атрибут PostSharp, чтобы проверить, находится ли имя метода в текущей трассировке стека.
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool IsReEntry() {
StackTrace stack = new StackTrace();
StackFrame[] frames = stack.GetFrames();
if (frames.Length < 2)
return false;
string currentMethod = frames[1].GetMethod().Name;
for (int i = 2; i < frames.Length; i++) {
if (frames[i].GetMethod().Name == currentMethod) {
return true;
}
}
return false;
}
Это сработает только в том случае, если повторный вход происходит в том же потоке, но все равно выглядит интересно.
Я бы рассмотрел повторный вход только в том же потоке. Если повторный вход означает несколько потоков, как насчет доменов приложений?
Эта тема немного устарела, но я подумал, что стоит перенести ее в 2012 год, потому что эта проблема все еще существует (более или менее). Мне удалось решить эту проблему, используя прокси-объекты, созданные с помощью Reflection.Emit (в частности, с использованием LinFu.DynamicProxy). Статья LinFu старше этой статьи, поэтому я предполагаю, что все, что в ней обсуждается, было актуальным, когда ее спрашивали (и все же почему-то все еще остается сегодня).
Я использовал LinFu, потому что я уже использовал его для других целей, но я уверен, что некоторые из других доступных фреймворков DynamicProxy подойдут вам (например, Castle.DynamicProxy), или вы можете использовать свои собственные на основе Reflection.Emit (не для тех со слабым нравом). Они предоставляют механизм, который выполняет большую часть роли АОП, сохраняя при этом контроль над кодом.
Возможно, вы захотите избежать повторного входа, изменив свой дизайн так, чтобы он никогда не вызывал function1 () до завершения предыдущего вызова. Мне кажется, что сверху не хватает слоя function1 ().
Хорошо, @Rob, я ценю это.
Какова область действия этого ограничения при наличии нескольких потоков и нескольких экземпляров объектов? Может ли функция function1 выполняться в любой момент только одним потоком для одного экземпляра объекта или это более расслабленно?