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 がスタック上で発生します。