Могу ли я добавить атрибут к функции, чтобы предотвратить повторный вход?

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

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

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

Какова область действия этого ограничения при наличии нескольких потоков и нескольких экземпляров объектов? Может ли функция function1 выполняться в любой момент только одним потоком для одного экземпляра объекта или это более расслабленно?

Constantin 24.11.2008 19:05
Асинхронная передача данных с помощью sendBeacon в JavaScript
Асинхронная передача данных с помощью sendBeacon в JavaScript
В современных веб-приложениях отправка данных из JavaScript на стороне клиента на сервер является распространенной задачей. Одним из популярных...
Как подобрать выигрышные акции с помощью анализа и визуализации на Python
Как подобрать выигрышные акции с помощью анализа и визуализации на Python
Отказ от ответственности: Эта статья предназначена только для демонстрации и не должна использоваться в качестве инвестиционного совета.
Принципы ООП в JavaScript
Принципы ООП в JavaScript
Парадигма объектно-ориентированного программирования имеет 4 основных принципа,
Пройдите собеседование по Angular: Общие вопросы и ответы экспертов
Пройдите собеседование по Angular: Общие вопросы и ответы экспертов
Можете ли вы объяснить разницу между ngOnInit и конструктором в Angular?
Laravel с Turbo JS
Laravel с Turbo JS
Turbo - это библиотека JavaScript для упрощения создания быстрых и высокоинтерактивных веб-приложений. Она работает с помощью техники под названием...
Типы ввода HTML: Лучшие практики и советы
Типы ввода HTML: Лучшие практики и советы
HTML, или HyperText Markup Language , является стандартным языком разметки, используемым для создания веб-страниц. Типы ввода HTML - это различные...
8
1
4 438
9

Ответы 9

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

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

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" не исправит это состояние гонки.

Constantin 24.11.2008 18:25

Без переписывания сборки и 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 ().

Это по-прежнему вызывает проблемы, присущие многопоточным средам. См. Мой пост о летучих переменных выше.

Rob Stevenson-Leggett 24.11.2008 14:42

Конечно, Роб. Это просто демонстрация, потому что она также обрабатывает только очень специфическую сигнатуру функции.

Barry Kelly 24.11.2008 20:56

Вместо того, чтобы использовать 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))

FunctorSalad 11.03.2011 03:54

Нет особой причины. Я много занимаюсь программированием на Win32, и связанные с этим функции занимают много времени. Использование int не повлияет на приведенный выше пример.

Sean 11.03.2011 10:38

Вы знаете, что в C# long определяется как int64, верно?

Theraot 16.02.2014 22:47

Ваш Win32 api определен так, чтобы принимать int32, если вы используете C++ для таргетинга Win32, как int, так и long определены как int32. И ... ладно, тебе все равно.

Theraot 17.02.2014 13:45

Я предполагаю, что m_InFunction - это общая переменная среди потоков (иначе, в чем смысл решения: P) - с учетом имени, поля члена класса и CompareExchange покрывает вход в защищенную область, но я ошибаюсь в предполагая, что Interlocked.Exchange(ref m_InFunction, 0), вместо m_InFunction=0 в блоке finally необходимо на выходе, чтобы гарантировать, что другие потоки подхватят новое значение?

mdip 26.03.2019 05:58

Вы можете создать атрибут 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;
    }

Это сработает только в том случае, если повторный вход происходит в том же потоке, но все равно выглядит интересно.

Simon 26.11.2008 11:42

Я бы рассмотрел повторный вход только в том же потоке. Если повторный вход означает несколько потоков, как насчет доменов приложений?

Bob 26.11.2008 19:15

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

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

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

Хорошо, @Rob, я ценю это.

Chris 23.02.2016 17:58

Другие вопросы по теме