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

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

Этот поток обновления спит и обновляется в бесконечном цикле. Когда основной поток завершается (через 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 с двумя или более экземплярами. Предположим также, что у вас есть работа, которую оба экземпляра могут выполнить за вас. При использовании метода рабочих ролей 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);
        }
    }

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

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

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


person danludwig    schedule 25.12.2013    source источник
comment
установка isRenewing на false приведет к корректному завершению потока, верно? Я что-то упускаю? Я не уверен насчет RenewLease метода, хотя   -  person Sriram Sakthivel    schedule 25.12.2013
comment
Я обновлю вопрос, чтобы предоставить больше информации.   -  person danludwig    schedule 25.12.2013
comment
Нет подходящих мест для вызова Thread.Abort ().   -  person Kris Vandermotten    schedule 26.12.2013


Ответы (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.

person Jim Mischel    schedule 26.12.2013
comment
В этом случае _isRenewing не нужно помечать как volatile, верно? Раз уж второй нить с него читать нет? - person danludwig; 27.12.2013
comment
_isRenewing не обязательно должно быть volatile. В идеале вы должны использовать событие или токен отмены, а не флаг Boolean, но в этом случае флаг будет работать нормально. - person Jim Mischel; 28.12.2013

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

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

person Sriram Sakthivel    schedule 25.12.2013