Это подходящее место для вызова Thread.Abort()?

У меня есть код, который я позаимствовал у Стива Маркса. Основной блок используется в потоке рабочих ролей Azure для получения аренды на BLOB-объект Azure. Это обеспечивает механизм блокировки для синхронизации между несколькими рабочими экземплярами, когда вы хотите, чтобы только один экземпляр обрабатывал задание одновременно. Однако, поскольку у вас могут быть задания, выполнение которых займет больше времени, чем тайм-аут аренды BLOB-объектов, новый поток создается для возобновления аренды BLOB-объектов очень часто.

Эта нить обновления спит и обновляется по бесконечной петле. Когда основной поток выходит (через Dispose в классе "потребитель", renewalThread.Abort() вызывается. Это вызывает все виды ThreadAbortExceptionбыть брошенным в рабочую роль.

Мне интересно, это лучший способ справиться с этим? Что мне не нравится в этом, так это то, что у вас может быть несколько потоков обновления, которые остаются спящими после того, как потребитель, который их породил, был уничтожен. Есть что-нибудь плохое в коде ниже? Если так, есть ли лучший способ? Или Thread.Abort() уместно здесь?

public class AutoRenewLease : IDisposable
{
    private readonly CloudBlockBlob _blob;
    public readonly string LeaseId;
    private Thread _renewalThread;
    private volatile bool _isRenewing = true;
    private bool _disposed;

    public bool HasLease { get { return LeaseId != null; } }

    public AutoRenewLease(CloudBlockBlob blob)
    {
        _blob = blob;

        // acquire lease
        LeaseId = blob.TryAcquireLease(TimeSpan.FromSeconds(60));
        if (!HasLease) return;

        // keep renewing lease
        _renewalThread = new Thread(() =>
        {
            try
            {
                while (_isRenewing)
                {
                    Thread.Sleep(TimeSpan.FromSeconds(40.0));
                    if (_isRenewing)
                        blob.RenewLease(AccessCondition
                            .GenerateLeaseCondition(LeaseId));
                }
            }
            catch { }
        });
        _renewalThread.Start();
    }

    ~AutoRenewLease()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;
        if (disposing && _renewalThread != null)
        {
            //_renewalThread.Abort();
            _isRenewing = false;
            _blob.ReleaseLease(AccessCondition
                .GenerateLeaseCondition(LeaseId));
            _renewalThread = null;
        }
        _disposed = true;
    }
}

Обновить

Допустим, у вас есть рабочая роль Azure, развернутая с 2 или более экземплярами. Предположим также, что у вас есть работа, которую оба экземпляра могут обработать для вас. Во время рабочих ролей Run метод у вас может быть что-то вроде этого:

    public override void Run()
    {
        while (true)
        {
            foreach (var task in _workforce)
            {
                var job = task.Key;
                var workers = task.Value;
                foreach (var worker in workers)
                    worker.Perform((dynamic)job);
            }
            Thread.Sleep(1000);
        }
    }

Каждую секунду роль будет проверять, запланированы ли для выполнения определенные задания, и, если они есть, обрабатывать их. Однако, чтобы избежать того, чтобы оба экземпляра ролей обрабатывали одно и то же задание, вы сначала берете в аренду объект BLOB-объекта. При этом другой экземпляр не может получить доступ к BLOB-объекту, поэтому он эффективно блокируется до тех пор, пока первый экземпляр не завершит обработку. (Примечание: получение нового лизинга происходит в методе.Perform выше.)

Теперь, скажем, работа может занять от 1 до 100 секунд. Существует встроенный тайм-аут для аренды блобов, поэтому, если вы хотите, чтобы другая роль была заблокирована до завершения процесса, вам придется периодически продлевать этот срок аренды, чтобы сохранить его в тайм-ауте. Это то, что включает в себя вышеупомянутый класс - автоматическое продление аренды, пока вы не распорядитесь ею как потребителем.

Мой вопрос в основном о тайм-ауте сна в Обновление темы. Скажите, что задание выполнено за 2 секунды. Обновление Thread будет корректно завершаться (я думаю), но не в течение еще 38 секунд. В этом и заключается суть неопределенности в моем вопросе. Исходный код вызвал renewalThread.Abort(), что привело к его немедленному прекращению. Лучше сделать это или оставить спать и уйти изящно в более позднее время? Если вы бьетесь о роли Run метод раз в секунду, вы можете иметь до 40 из этих потоков обновления, ожидающих корректного выхода. Если у вас есть разные задания, блокирующие разные BLOB-объекты, это число умножается на количество выделенных BLOB-объектов. Однако, если вы сделаете это с Thread.Abort (), вы получите столько же исключений ThreadAbortExceptions в стеке.

2 ответа

Решение

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

Вам не нужна нить в цикле сна. Тебе нужен таймер. Например:

public class AutoRenewLease : IDisposable
{
    private readonly CloudBlockBlob _blob;
    public readonly string LeaseId;
    private System.Threading.Timer _renewalTimer;
    private volatile bool _isRenewing = true;
    private bool _disposed;

    public bool HasLease { get { return LeaseId != null; } }

    public AutoRenewLease(CloudBlockBlob blob)
    {
        _blob = blob;

        // acquire lease
        LeaseId = blob.TryAcquireLease(TimeSpan.FromSeconds(60));
        if (!HasLease) return;

        _renewalTimer = new System.Threading.Timer(x =>
        {
            if (_isRenewing)
            {
                blob.RenewLease(AccessCondition
                    .GenerateLeaseCondition(LeaseId));
            }
        }, null, TimeSpan.FromSeconds(40), TimeSpan.FromSeconds(40));


    ~AutoRenewLease()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;
        if (disposing && _renewalTimer != null)
        {
            _isRenewing = false;
            _renewalTimer.Dispose();
            _blob.ReleaseLease(AccessCondition
                .GenerateLeaseCondition(LeaseId));
            _renewalTimer = null;
        }
        _disposed = true;
    }
}

Нет необходимости тратить ресурсы, используемые потоком, просто чтобы он мог спать большую часть времени. Использование таймера исключает опрос, а также устраняет необходимость Thread.Abort,

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

Сделать это просто с ManualResetEvent, Это остановит вашу ветку изящно и сразу без использования Abort,

private ManualResetEvent jobSignal = new ManualResetEvent(false);
public AutoRenewLease(CloudBlockBlob blob)
{
    _blob = blob;

    // acquire lease
    LeaseId = blob.TryAcquireLease(TimeSpan.FromSeconds(60));
    if (!HasLease) return;

    // keep renewing lease
    _renewalThread = new Thread(() =>
    {
        try
        {
            while (_isRenewing)
            {
                if(jobSignal.WaitOne(TimeSpan.FromSeconds(40.0)))
                {
                    //Disposed so stop working
                    jobSignal.Dispose();
                    jobSignal = null;
                    return;
                }
                if (_isRenewing)
                    blob.RenewLease(AccessCondition
                        .GenerateLeaseCondition(LeaseId));
            }
        }
        catch (Exception ex) {//atleast log it }
    });
    _renewalThread.Start();
}

protected virtual void Dispose(bool disposing)
{
    if (_disposed) return;
    if (disposing && _renewalThread != null)
    {
        jobSignal.Set();//Signal the thread to stop working
        _isRenewing = false;
        _blob.ReleaseLease(AccessCondition
            .GenerateLeaseCondition(LeaseId));
        _renewalThread = null;
    }
    _disposed = true;
}

Надеюсь это поможет.

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