0

我有一些从 Steve Marx 那里借来的代码。主块在 azure worker 角色线程中用于获取 azure blob 的租约。当您只希望一个实例一次处理一个作业时,这提供了一种锁定机制,用于在多个工作实例之间进行同步。但是,由于您的作业可能需要比 blob 租约超时时间更长的时间才能完成,因此会产生一个新线程来每隔一段时间更新 blob 租约。

此更新线程在无限循环中休眠和更新。当主线程退出时(通过Dispose类的消费者),renewalThread.Abort()被调用ThreadAbortException这导致在worker角色中抛出各种s。

我想知道,这是处理这个问题的更好方法吗?我不喜欢的是,在产生它们的消费者被处理后,你可以有几个保持休眠状态的更新线程。下面的代码有什么不好的地方吗?如果是这样,有没有更好的方法?还是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;
    }
}

更新

假设您有一个部署了 2 个或更多实例的 azure worker 角色。还假设您有一份工作,两个实例都有能力为您处理。在工作角色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 秒才能完成。Blob 租约有一个内置的超时,因此如果您想在进程完成之前阻止其他角色,您必须定期更新该租约,以使其保持超时。这就是上面的类所封装的——自动更新租约,直到您将其作为消费者处理。

我的问题主要是关于renewThread中的睡眠超时。说工作在 2 秒内完成。更新线程将优雅地退出(我认为),但不会再过 38 秒。这就是我的问题的不确定性所在。原始代码调用了renewThread.Abort(),导致它立即停止。这样做更好,还是让它休眠并在以后优雅地退出?如果您Run每秒一次对角色的方法进行心跳检测,则最多可能有 40 个此类更新线程等待正常退出。如果您有不同的作业阻塞在不同的 blob 上,则该数字将乘以租用的 blob 数。但是,如果您使用 Thread.Abort() 执行此操作,您会在堆栈上得到同样多的 ThreadAbortExceptions。

4

2 回答 2

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.

于 2013-12-26T16:39:29.983 回答
1

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

希望这可以帮助。

于 2013-12-25T20:40:12.140 回答