Определите поток, который содержит блокировку
один из потоков в моем приложении заблокирован в следующем операторе блокировки и привел к тупиковой ошибке
void ExecuteCommand()
{
lock(this._lockinstance)
{
// do some operation
}
}
Можно ли легко определить, какой поток в данный момент держит блокировку?.. Мое приложение имеет более 50 потоков, что затрудняет переход через каждый столбец с помощью visual studio для поиска потока, который содержит блокировку
Ответы
Ответ 1
Пример кода для тестирования:
class Test {
private object locker = new object();
public void Run() {
lock (locker) { // <== breakpoint here
Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
}
}
}
Установите точку останова на указанной строке. Когда он ломается, используйте Debug + Windows + Memory + Memory 1. Щелкните правой кнопкой мыши по окну и выберите "4-байтное целое число". В поле Адрес введите & locker. Второе слово - это идентификатор потока нити, которому принадлежит блокировка. Пройдите мимо инструкции блокировки, чтобы увидеть ее изменение.
Помните, что число - это идентификатор управляемого потока, а не идентификатор потока операционной системы, который вы видите в окне Debug + Windows + Threads. Это похоже на отстой, вы, вероятно, должны добавить в свою программу некоторые записи, которые выгружают значение ManagedThreadId, чтобы у вас был способ сопоставить значение с потоком. Обновление: исправлено в последующих версиях VS, окно Debug > Windows > Threads debugger теперь показывает ManagedThreadId.
Ответ 2
Вы можете реализовать оболочку монитора, которая сохраняет записи трассировки стека и имена потоков при вводе.
Старый способ:
private object myLock = new object();
...
lock(myLock)
{
DoSomething();
}
...
С кодом ниже:
private SmartLock myLock = new SmartLock();
...
myLock.Lock( () =>
{
DoSomething();
}
);
...
Источник:
public class SmartLock
{
private object LockObject = new object();
private string HoldingTrace = "";
private static int WARN_TIMEOUT_MS = 5000; //5 secs
public void Lock(Action action)
{
try
{
Enter();
action.Invoke();
}
catch (Exception ex)
{
Globals.Error("SmartLock Lock action", ex);
}
finally
{
Exit();
}
}
private void Enter()
{
try
{
bool locked = false;
int timeoutMS = 0;
while (!locked)
{
//keep trying to get the lock, and warn if not accessible after timeout
locked = Monitor.TryEnter(LockObject, WARN_TIMEOUT_MS);
if (!locked)
{
timeoutMS += WARN_TIMEOUT_MS;
Globals.Warn("Lock held: " + (timeoutMS / 1000) + " secs by " + HoldingTrace + " requested by " + GetStackTrace());
}
}
//save a stack trace for the code that is holding the lock
HoldingTrace = GetStackTrace();
}
catch (Exception ex)
{
Globals.Error("SmartLock Enter", ex);
}
}
private string GetStackTrace()
{
StackTrace trace = new StackTrace();
string threadID = Thread.CurrentThread.Name ?? "";
return "[" + threadID + "]" + trace.ToString().Replace('\n', '|').Replace("\r", "");
}
private void Exit()
{
try
{
Monitor.Exit(LockObject);
HoldingTrace = "";
}
catch (Exception ex)
{
Globals.Error("SmartLock Exit", ex);
}
}
}
Ответ 3
Да, есть вид "Threads", который вы можете использовать в VS. Перерыв в любом месте вашего приложения (или нажмите кнопку "Разбить все" ), затем вы можете выбрать каждый поток и просмотреть, у кого есть блокировка (если есть).
Чтобы добавить его, перейдите в Debug > Windows > Темы (Ctrl + D, T)
Ответ 4
Недавно я пытался определить, какая функция хранит блокировку, и нашел следующее очень полезное и не видел нигде раньше. Я поставил его в качестве ответа здесь, если другие считают это полезным.
Многие из ранее опубликованных решений требуют написания нового класса, а затем преобразования всех блокировок (blah) в BetterLock (blah), которые являются большой частью работы для отладки и которые вы, возможно, не захотите в выпуске/ваш код. Другие потребовали подключения отладчика, который меняет время кодирования и может скрывать проблему.
Вместо этого попробуйте следующее...
Исходный код:
object obj = new object();
lock(obj)
{
// Do stuff
}
Измененный код для отладки:
object _obj = new object();
object obj
{
get
{
System.Diagnostics.StackFrame frame = new System.Diagnostics.StackFrame(1);
System.Diagnostics.Trace.WriteLine(String.Format("Lock acquired by: {0} on thread {1}", frame.GetMethod().Name, System.Threading.Thread.CurrentThread.ManagedThreadId));
return _obj;
}
}
// Note that the code within lock(obj) and the lock itself remain unchanged.
lock(obj)
{
// Do stuff
}
Выставляя obj
как свойство, по крайней мере временно, с очень минимальными изменениями кода, вы можете определить, какая функция приобрела последнюю блокировку и какой поток - просто посмотрите на вывод Trace для последней записи. Конечно, вы можете выводить любую другую информацию, которая может оказаться полезной и в геттере.
Нет, это не позволит вам определить, когда была выпущена блокировка, но если она будет выпущена своевременно, тогда у вас на самом деле не было проблемы с блокировкой.
Ответ 5
Управляемый проводник стека из http://mse.codeplex.com/ или http://www.microsoft.com/downloadS/details.aspx?FamilyID=80cf81f7-d710-47e3-8b95-5a6555a230c2&displaylang=en отлично в таких случаях.
Он перехватывает запущенный управляемый код (необходимые разрешения), включая живой код, и захватывает список запущенных потоков. Вы можете дважды щелкнуть по любому из них или (более полезно в таких случаях) выбрать лот и нажать enter для быстрого относительно неинвазивного (очевидно, что он будет потреблять ресурсы, но он идет и выходит так быстро, как может ) дамп текущих стеков разных потоков. Отлично подходит для поиска тупика, бесконечной петли, почти бесконечной петли (в те времена, когда ваше приложение случайно зависит от того, как астрономы пессимистично относятся к тому, как долго будет продолжаться земля, чтобы надеяться на завершение) и в других подобных случаях.
Ответ 6
Старые сообщения старые.
Но я думал, что могу дать решение, которое я считаю довольно полезным для поиска мертвых блокировок и других проблем с блокировкой.
Я использую одноразовый класс для моей блокировки - мне нравится Monitor
, но можно использовать любой механизм блокировки.
public class MonitorLock : IDisposable
{
public static MonitorLock CreateLock(object value)
{
return new MonitorLock(value);
}
private readonly object _l;
protected MonitorLock(object l)
{
_l = l;
Console.WriteLine("Lock {0} attempt by {1}", _l, Thread.CurrentThread.ManagedThreadId);
Monitor.Enter(_l);
Console.WriteLine("Lock {0} held by {1}" , _l, Thread.CurrentThread.ManagedThreadId);
}
public void Dispose()
{
Monitor.Exit(_l);
Console.WriteLine("Lock {0} released by {1}", _l, Thread.CurrentThread.ManagedThreadId);
}
}
Я использую объект блокировки с именем, чтобы я мог понять, какой замок я пытаюсь выполнить.
public class LockObject
{
public string Name { get; set; }
public LockObject(string name)
{
Name = name;
}
public override string ToString()
{
return Name;
}
}
Наконец, создайте объект блокировки, а затем в используемом блоке удерживайте объект.
//create an object to lock on
private readonly object _requestLock = new LockObject("_requestLock");
using (MonitorLock.CreateLock(_requestLock))
{
//do some work
}
Выход должен быть чем-то вроде строк
Lock _requestLock attempt by 92
Lock _requestLock held by 92
Lock _requestLock attempt by 19
Lock _requestLock released by 92
Lock _requestLock held by 19
Lock _requestLock released by 19
Надеюсь, что кто-то найдет это полезным:)