Блокировка зависает при вызове из интерфейса пользователя в System.Threading.Thread

РЕДАКТИРОВАТЬ: см. Историю вопроса, для неизмененного вопроса, чтобы не отменять комментарии.

Я нажимаю кнопку, которая выполняет определенные коды и создает stream (System.Threading.Thread). Когда я возвращаю кнопку, которая запускает процесс, она зависает и зависает ui. Что может быть причиной?

public partial class ucLoader : UserControl { //lock object for whole instance of class ucLoader private object lockUcLoader = new object(); //bringing info from ui private void btnBringInfo_Click(object sender, EventArgs e) { lock (lockUcLoader) { btnBringInfo_PerformClick(false); } } //using this method because it could be called when even button not visible internal void btnBringInfo_PerformClick(bool calledFromBandInit) { lock (lockUcLoader) //HANGS HERE when called multiple times and ui freeze as well //by the way I am using (repetitive) lock, because this method also called independently from btnBringInfo_Click { //... this.btnLoad_PerformClick(); } } //Another button perform click that could be triggered elsewhere when even button not visible private void btnLoad_PerformClick() { lock (lockUcLoader) //I am using (repetitive) lock, because this method also called independently from btnBringInfo_PerformClick { //... Run(); } } //method for creating thread which System.Threading.Thread private void Run() { lock (lockUcLoader) //Maybe this lock is NOT REQUIRED, as it is called by only btnLoad_PerformClick(), could you please confirm? { //some code that thread can be killed when available, you can ingore this two lines as they are irrelevant to subject, I think Source = new CancellationTokenSource(); Token = Source.Token; var shell = new WindowsShell(); Thread = new Thread((object o) => { //... var tokenInThread = (CancellationToken)o; exitCode =TaskExtractBatchFiles(cls, shell, exitCode); using (var logEnt = new logEntities()) { //Do some db operation //... this.Invoke((MethodInvoker)delegate { //do some ui update operation //... }); } } Thread.Start(Token); } } public void Progress(string message) { Invoke((MethodInvoker)delegate //ATTENTION HERE see below picture Wait occurs here { if (message != null && message.Trim() != string.Empty) { this.txtStatus.AppendText(message + Environment.NewLine); } }); } } 

Чтобы избежать вопроса о закрытии вопроса, каким может быть мой вопрос, как я могу предотвратить использование метода ниже, можно получить доступ с блокировкой из фонового streamа и нити ui

 public void Progress(string message) { Invoke((MethodInvoker)delegate //ATTENTION HERE see below picture Wait occurs here { if (message != null && message.Trim() != string.Empty) { this.txtStatus.AppendText(message + Environment.NewLine); } }); } 

введите описание изображения здесь

введите описание изображения здесь

  Invoke((MethodInvoker)delegate ... 

Всякий раз, когда вы используете оператор lock в своем коде, вы всегда рискуете вызвать тупик . Один из classических ошибок в streamе. Вам обычно нужно как минимум два замка, чтобы добраться туда, приобретя их в неправильном порядке. И да, в вашей программе два. Тот, о котором вы сами заявили. И тот, который вы не видите, потому что он похоронен внутри водопровода, что делает работу Control.Invoke (). Невозможность увидеть блокировку – вот то, что заставляет тупик сложной проблемой отлаживать.

Вы можете это обосновать, блокировка внутри Control.Invoke необходима, чтобы гарантировать, что рабочий stream заблокирован до тех пор, пока stream пользовательского интерфейса не выполнит цель делегирования. Вероятно, также помогает выяснить, почему программа зашла в тупик. Вы начали рабочий stream, он приобрел блокировку lockUcLoader и начинает выполнять свою работу, вызывая Control.Invoke при этом. Теперь вы нажимаете кнопку до того, как рабочий сделан, он обязательно блокируется. Но это делает stream пользовательского интерфейса кататоническим и больше не способен выполнять код Control.Invoke. Таким образом, рабочий stream зависает в вызове Invoke и не освобождает блокировку. И пользовательский интерфейс висит навсегда на замке, так как рабочий не может завершить, тупиковый город.

Control.Invoke датируется .NET 1.0, версией структуры, которая имеет несколько серьезных ошибок дизайна в коде, связанных с streamовой обработкой. Будучи полезными, они просто устанавливают смертельные ловушки для программистов. Что уникально в Control.Invoke, так это то, что использовать его не всегда правильно.

Различать Control.Invoke и Control.BeginInvoke. Вам когда-либо понадобится Invoke, когда вам нужно его возвращаемое значение. Обратите внимание, как вы этого не сделаете, используя BeginInvoke, достаточно хорошо и мгновенно решает тупик. Вы можете использовать Invoke для получения значения из пользовательского интерфейса, чтобы вы могли использовать его в рабочем streamе. Но это вызывает другие серьезные проблемы с streamами – ошибка с streamовой расстановкой, рабочий не имеет представления о том, в каком состоянии находится пользовательский интерфейс. Скажем, пользователь может быть занят взаимодействием с ним, набрав новое значение. Вы не можете знать, какое значение вы получите, это будет легко устаревшее старое значение. Неизбежно возникает несоответствие между пользовательским интерфейсом и выполняемой работой. Единственный способ избежать этого несчастья – запретить пользователю вводить новое значение, легко выполняемое с помощью Enable = false. Но теперь уже нет смысла использовать Invoke, вы можете также передать значение при запуске streamа.

Поэтому использование BeginInvoke уже достаточно хорошо для решения проблемы. Но это не то место, где вы должны остановиться. Для этих блокировок в обработчиках событий Click нет никакого смысла, все, что они делают, это сделать пользовательский интерфейс невосприимчивым, сильно запутав пользователя. Вместо этого вы должны установить для «Включить» свойства этих кнопок значение « false» . Верните их в true, когда рабочий закончится. Теперь это больше не может пойти не так, вам не нужны блокировки, и пользователь получает хорошие отзывы.

Есть еще одна серьезная проблема, с которой вы еще не столкнулись, но вы должны обратиться. Пользователь UserControl не имеет контроля над своей жизнью, он становится доступным, когда пользователь закрывает форму, на которой он размещен. Но это полностью не синхронизируется с исполнением рабочего streamа, оно продолжает вызывать BeginInvoke, хотя элемент управления мертв как doornail. Это сделает вашу программу бомбой, надеюсь, на ObjectDisposedException. Ошибки с чередованием, которые блокировка не может решить. Форма должна помочь, она должна активно запрещать пользователю закрывать ее. Некоторые замечания об этой ошибке в этом Q + A.

Для полноты я должен упомянуть третью наиболее часто встречающуюся ошибку с streamами, от которой, вероятно, страдает такой код. У него нет официального названия, я называю это «ошибкой firehose». Это происходит, когда рабочий stream слишком часто вызывает BeginInvoke, что слишком сильно увеличивает stream пользовательского интерфейса. Бывает легко, называя его более тысячи раз в секунду, как правило, достаточно. Поток пользовательского интерфейса начинает записывать 100% -ное kernel, пытаясь не отставать от запросов на вызовы и никогда не сможет догнать. Легко видеть, он перестает рисовать себя и реагирует на ввод, обязанности, выполняемые с более низким приоритетом. Это необходимо исправить логическим путем, обновляя пользовательский интерфейс более 25 раз в секунду, создавая размытие, которое человеческий глаз не может наблюдать, и поэтому бессмысленно.