Должен ли я использовать IDisposable для чисто управляемых ресурсов? - PullRequest
2 голосов
/ 06 апреля 2010

Вот сценарий:

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

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

Вы бы использовали это так:

var transaction = new Transaction();

using (var tlock = transaction.Lock())
{
    transaction.Update(data, tlock);
}

Теперь я хочу, чтобы класс TransactionLock реализовал IDisposable, чтобы его использование было понятно. Но у меня нет никаких неуправляемых ресурсов, чтобы распоряжаться. однако сам объект TransctionLock является своего рода «неуправляемым ресурсом» в том смысле, что CLR не знает, как его правильно завершить.

Все это было бы хорошо, я бы просто использовал IDisposable и покончил бы с этим.

Однако моя проблема возникает, когда я пытаюсь сделать это в финализаторе:

~TransactionLock()
{
    this.Dispose(false);
}

Я хочу, чтобы финализатор освободил транзакцию из блокировки, если это возможно. Как в финализаторе определить, завершена ли родительская транзакция (this.transaction)?

Есть ли лучший шаблон, который я должен использовать?

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

<Ч />

Класс Transaction выглядит примерно так:

public sealed class Transaction
{
    private readonly object lockMutex = new object();

    private TransactionLock currentLock;

    public TransactionLock Lock()
    {
        lock (this.lockMutex)
        {
            if (this.currentLock != null)
                throw new InvalidOperationException(/* ... */);

            this.currentLock = new TransactionLock(this);
            return this.currentLock;
        }
    }

    public void Update(object data, TransactionLock tlock)
    {
        lock (this.lockMutex)
        {
            this.ValidateLock(tlock);

            // ...
        }
    }

    internal void ValidateLock(TransactionLock tlock)
    {
        if (this.currentLock == null)
            throw new InvalidOperationException(/* ... */);

        if (this.currentLock != tlock)
            throw new InvalidOperationException(/* ... */);
    }

    internal void Unlock(TransactionLock tlock)
    {
        lock (this.lockMutex)
        {
            this.ValidateLock(tlock);

            this.currentLock = null;
        }
    }
}

И Dispose(bool) код для TransactionLock:

private void Dispose(bool disposing)
{
    if (disposing)
    {
        if (this.Transaction != null)
        {
            this.Transaction.Unlock(this);
            this.Transaction = null;
        }
    }
}

Ответы [ 2 ]

2 голосов
/ 06 апреля 2010

Это было обсуждалось ранее .Ваш случай намного проще, вы также используете финализатор.Это в корне неправильно, вы скрываете ошибку в клиентском коде.Помните, что финализаторы работают в отдельном потоке.Отладка согласованной тупиковой ситуации намного проще, чем работа с блокировками, которые исчезают случайно и асинхронно.

Рекомендация: следуйте указаниям платформы .NET: не слишком помогайтеMicrosoft отказалась от синхронизированного метода по той же причине.

1 голос
/ 06 апреля 2010

Как в финализаторе определить, родительская транзакция (this.transaction) уже была завершено

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

   ~TransactionLock()
    {
        this.Dispose(false);
    }

Есть ли лучший образец, которым я должен быть используя

Если правильно, что TransactionLock не имеет неуправляемых ресурсов, просто опустите деструктор (финализатор). Он не имеет функции, но имеет значительную стоимость.

Редактировать: Если я правильно прочитал, Unlock не обрезает связь между TransactionLock и TTransaction, а это значит, что через деструктор будут вызываться старые блокировки Dispose (bool). Не ясно, безопасно ли это.

Вопрос будет немного более полным с кодом TransactionLock.Dispose(bool)


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

Из этого следует, что когда собран TransactionLock, он может содержать только ссылку на транзакцию, которая также собирается. Здесь не нужно вмешиваться в деструкторы, которые ничего не решат и могут создать только те проблемы, которые вам не нужны.

...