这是调用Thread.Abort()的合适位置吗?

时间:2013-12-25 18:22:46

标签: c# multithreading abort thread-abort azure-storage-blobs

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

此续订线程在无限循环中休眠和更新。主线程退出时(通过类'消费者中的Dispose} renewalThread.Abort() is invoked。这会导致所有类型的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;
    }
}

更新

假设您使用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租约存在内置超时,因此如果要在进程完成之前保持其他角色被阻止,则必须定期续订该租约,以使其保持超时。这就是上面的类所封装的内容 - 自动续订租约,直到您将其作为消费者处理。

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

2 个答案:

答案 0 :(得分: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

答案 1 :(得分: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;
}

希望这有帮助。