0

Steve Marx から借りたコードがいくつかあります。メイン ブロックは、Azure ワーカー ロール スレッドで使用され、Azure BLOB のリースを取得します。これにより、一度に 1 つのインスタンスだけでジョブを処理したい場合に、複数のワーカー インスタンス間で同期するためのロック メカニズムが提供されます。ただし、BLOB リースのタイムアウトよりも完了するまでに時間がかかるジョブがある可能性があるため、BLOB リースを頻繁に更新するために新しいスレッドが生成されます。

この更新スレッドはスリープ状態になり、無限ループで更新されます。メイン スレッドが (Disposeクラスのコンシューマーを介して)終了するrenewalThread.Abort()と、呼び出されます。これにより、すべての種類のThreadAbortExceptionが worker ロールでスローされます。

私は疑問に思っています、これはこれを処理するためのより良い方法ですか?私が気に入らないのは、それらを生成したコンシューマーが破棄された後も、いくつかの更新スレッドがスリープ状態のままになる可能性があることです。以下のコードに何か悪い点はありますか? もしそうなら、より良い方法はありますか?それとも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 ワーカー ロールがあるとします。また、両方のインスタンスが処理できるジョブがあるとします。ワーカー ロール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メソッドを 1 秒に 1 回ハートビートしている場合、正常に終了するために最大 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 に答える