Это подходящее место для вызова Thread.Abort()?
У меня есть код, который я позаимствовал у Стива Маркса. Основной блок используется в потоке рабочих ролей Azure для получения аренды на BLOB-объект Azure. Это обеспечивает механизм блокировки для синхронизации между несколькими рабочими экземплярами, когда вы хотите, чтобы только один экземпляр обрабатывал задание одновременно. Однако, поскольку у вас могут быть задания, выполнение которых займет больше времени, чем тайм-аут аренды BLOB-объектов, новый поток создается для возобновления аренды BLOB-объектов очень часто.
Эта нить обновления спит и обновляется по бесконечной петле. Когда основной поток выходит (через 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, развернутая с 2 или более экземплярами. Предположим также, что у вас есть работа, которую оба экземпляра могут обработать для вас. Во время рабочих ролей 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 секунд. Существует встроенный тайм-аут для аренды блобов, поэтому, если вы хотите, чтобы другая роль была заблокирована до завершения процесса, вам придется периодически продлевать этот срок аренды, чтобы сохранить его в тайм-ауте. Это то, что включает в себя вышеупомянутый класс - автоматическое продление аренды, пока вы не распорядитесь ею как потребителем.
Мой вопрос в основном о тайм-ауте сна в Обновление темы. Скажите, что задание выполнено за 2 секунды. Обновление Thread будет корректно завершаться (я думаю), но не в течение еще 38 секунд. В этом и заключается суть неопределенности в моем вопросе. Исходный код вызвал renewalThread.Abort(), что привело к его немедленному прекращению. Лучше сделать это или оставить спать и уйти изящно в более позднее время? Если вы бьетесь о роли Run
метод раз в секунду, вы можете иметь до 40 из этих потоков обновления, ожидающих корректного выхода. Если у вас есть разные задания, блокирующие разные BLOB-объекты, это число умножается на количество выделенных BLOB-объектов. Однако, если вы сделаете это с Thread.Abort (), вы получите столько же исключений ThreadAbortExceptions в стеке.
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
,
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;
}
Надеюсь это поможет.