Могу ли я добавить атрибут в функцию, чтобы предотвратить повторный вход? - PullRequest
7 голосов
/ 24 ноября 2008

На данный момент у меня есть несколько функций, которые выглядят так:

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
    ...
}

Есть ли атрибут, который я могу добавить к функции, чтобы предотвратить повторный вход? Если нет, как бы я сделал один? Я слышал об атрибутах AOP, которые можно использовать для добавления кода до и после вызовов функций; они подойдут?

Ответы [ 9 ]

16 голосов
/ 24 ноября 2008

Вместо того, чтобы использовать bool и устанавливать его напрямую, попробуйте использовать класс long и Interlocked:

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
}

Это сделает проверку потока безопасной.

5 голосов
/ 24 ноября 2008

Без сборки и перезаписи 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 - вы можете написать другие варианты или полностью универсальную версию с большими усилиями) и альтернативную функцию для вызова в случае повторного входа. (Альтернативная функция может выдать исключение или немедленно вернуться и т. Д.) Затем во всем коде, где вы обычно вызываете переданный метод, вместо этого вы вызываете делегат, возвращаемый WrapAgainstReentry ().

4 голосов
/ 24 ноября 2008

Вы можете создать атрибут 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;
    }
2 голосов
/ 24 ноября 2008

Вы можете обнаружить, что для этого можете использовать PostSharp - вместе с советами Энтони по поводу использования try / finally. Это, вероятно, будет грязно, хотя. Также подумайте, хотите ли вы, чтобы повторный вход был для каждого потока или для каждого экземпляра. (Может ли несколько потоков вызывать метод для начала или нет?)

Ничего подобного нет в самой структуре.

1 голос
/ 14 июля 2012

Эта ветка устарела, но я решил, что стоит перенести ее в 2012, потому что эта проблема все еще существует (более или менее). Мне удалось решить эту проблему с помощью прокси-объектов, созданных с использованием Reflection.Emit (в частности, с использованием LinFu.DynamicProxy ). Статья LinFu старше этой статьи, поэтому я предполагаю, что все, что она обсуждает, было актуально, когда ее спросили (и все же как-то до сих пор).

Я использовал LinFu, потому что я уже использовал его для других целей, но я уверен, что некоторые из других доступных сред DynamicProxy будут работать для вас (например, Castle.DynamicProxy), или вы можете свернуть свой собственный на основе Reflection.Emit ( не для тех, у кого слабый нрав). Они предоставляют механизм, который выполняет большую часть роли АОП, сохраняя контроль над вашим кодом.

1 голос
/ 24 ноября 2008

Если это для безопасности потоков, вы должны быть осторожны с этой переменной.

Возможно, другой поток может войти в функцию и пройти проверку до того, как первый поток установит переменную.

Убедитесь, что он помечен как изменчивый:

private volatile bool inFunction1 = false;
1 голос
/ 24 ноября 2008

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

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

PS: используйте блок try ... finally в приведенном выше примере. В противном случае, если в середине функции возникнет исключение, inFunction1 останется истинным, и все вызовы вернутся немедленно.

например. :

if (inFunction1) 
   return;

try
{
  inFunction1 = true;

  // do stuff which might cause function1 to get called
  ...
}
finally 
{
  inFunction1 = false;
}
0 голосов
/ 22 февраля 2016

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

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

Я не думаю, что это будет возможно.

Ближайшим будет атрибут «Синхронизировано», но он заблокирует все последующие вызовы.

...