Как я могу заблокировать функцию для вызывающего и немедленно вернуть других вызывающих?

Ну, вы можете использовать Expression.AndAlso / OrElse и т. д. для объединения логических выражений, но проблема заключается в параметрах; вы работаете с тем же ParameterExpression в expr1 и expr2? Если это так, это проще:

var body = Expression.AndAlso(expr1.Body, expr2.Body);
var lambda = Expression.Lambda<Func<T,bool>>(body, expr1.Parameters[0]);

Это также хорошо работает, чтобы свести на нет одну операцию:

static Expression<Func<T, bool>> Not<T>(
    this Expression<Func<T, bool>> expr)
{
    return Expression.Lambda<Func<T, bool>>(
        Expression.Not(expr.Body), expr.Parameters[0]);
}

В противном случае, в зависимости от поставщика LINQ, вы можете скомбинируйте их с Invoke:

// OrElse is very similar...
static Expression<Func<T, bool>> AndAlso<T>(
    this Expression<Func<T, bool>> left,
    Expression<Func<T, bool>> right)
{
    var param = Expression.Parameter(typeof(T), "x");
    var body = Expression.AndAlso(
            Expression.Invoke(left, param),
            Expression.Invoke(right, param)
        );
    var lambda = Expression.Lambda<Func<T, bool>>(body, param);
    return lambda;
}

Где-то у меня есть код, который переписывает дерево выражений, заменяя узлы, чтобы удалить необходимость в Invoke, но он довольно длинный (и Я не могу вспомнить, где я его оставил ...)


Обобщенная версия, которая выбирает самый простой маршрут:

static Expression<Func<T, bool>> AndAlso<T>(
    this Expression<Func<T, bool>> expr1,
    Expression<Func<T, bool>> expr2)
{
    // need to detect whether they use the same
    // parameter instance; if not, they need fixing
    ParameterExpression param = expr1.Parameters[0];
    if (ReferenceEquals(param, expr2.Parameters[0]))
    {
        // simple version
        return Expression.Lambda<Func<T, bool>>(
            Expression.AndAlso(expr1.Body, expr2.Body), param);
    }
    // otherwise, keep expr1 "as is" and invoke expr2
    return Expression.Lambda<Func<T, bool>>(
        Expression.AndAlso(
            expr1.Body,
            Expression.Invoke(expr2, param)), param);
}

Начиная с .net 4.0. Существует класс ExpressionVistor, который позволяет создавать выражения, безопасные для EF.

    public static Expression<Func<T, bool>> AndAlso<T>(
        this Expression<Func<T, bool>> expr1,
        Expression<Func<T, bool>> expr2)
    {
        var parameter = Expression.Parameter(typeof (T));

        var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);
        var left = leftVisitor.Visit(expr1.Body);

        var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);
        var right = rightVisitor.Visit(expr2.Body);

        return Expression.Lambda<Func<T, bool>>(
            Expression.AndAlso(left, right), parameter);
    }



    private class ReplaceExpressionVisitor
        : ExpressionVisitor
    {
        private readonly Expression _oldValue;
        private readonly Expression _newValue;

        public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
        {
            _oldValue = oldValue;
            _newValue = newValue;
        }

        public override Expression Visit(Expression node)
        {
            if (node == _oldValue)
                return _newValue;
            return base.Visit(node);
        }
    }
1
задан W2a 18 January 2019 в 14:42
поделиться

1 ответ

У меня есть пара улучшений для вашего подхода.

1) Вам необходимо добавить блок try-finally, чтобы установить _beingUsed = false гарантированно. В противном случае, если трудоемкий код вызовет исключение, ни один вызывающий не сможет запустить этот код.

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

Вот улучшенный код:

public ErrorCodes SomeFunction()
{
    lock(_lock)
    {
        if(_beingUsed)
          return ErrorCodes.BeingUsed;

        _beingUsed = true;
    }

    // here can enter only one caller
    try
    {
        // Time consuming code that is not thread safe.
        return ErrorCodes.OK;
    }
    finally
    {
        _beingUsed = false;
    }
}

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

int _flag = 0;
public ErrorCodes SomeFunction()
{
    if (Interlocked.Exchange(ref _flag, 1) == 0)
    {
        // here can enter only one caller
        try
        {
            //process long operation
            return ErrorCodes.OK;
        }
        finally
        {
            Interlocked.Exchange(ref _flag, 0);
        }

    }
    else
    {
        //immediately return
        return ErrorCodes.BeingUsed;
    }
}

Но этот код выглядит более сложным и требует понимания программирования без блокировки.

0
ответ дан AlbertK 18 January 2019 в 14:42
поделиться
Другие вопросы по тегам:

Похожие вопросы: