Этот thread.abort () нормально и безопасно? - PullRequest
20 голосов
/ 07 января 2009

Я создал пользовательский элемент управления автозаполнения, когда пользователь нажимает клавишу, он запрашивает сервер базы данных (используя Remoting) в другом потоке. Когда пользователь печатает очень быстро, программа должна отменить ранее выполненный запрос / поток.

Ранее я сначала реализовал его как AsyncCallback, но я нахожу его громоздким, слишком много домашних правил (например, AsyncResult, AsyncState, EndInvoke) плюс вам нужно обнаружить поток объекта BeginInvoke'd, чтобы вы могли завершить ранее выполняющий поток. Кроме того, если бы я продолжил AsyncCallback, на тех AsyncCallbacks нет метода, который мог бы правильно завершить ранее выполняющийся поток.

EndInvoke не может завершить поток, он все равно завершит операцию потока, который должен быть завершен. Я бы все-таки использовал Abort () в потоке.

Так что я решил просто реализовать его с подходом чистого потока, без AsyncCallback. Этот thread.abort () нормален и безопасен для вас?

public delegate DataSet LookupValuesDelegate(LookupTextEventArgs e);

internal delegate void PassDataSet(DataSet ds);

public class AutoCompleteBox : UserControl
{
   Thread _yarn = null;

   [System.ComponentModel.Category("Data")]
   public LookupValuesDelegate LookupValuesDelegate { set; get; }

   void DataSetCallback(DataSet ds)
   {
      if (this.InvokeRequired)
         this.Invoke(new PassDataSet(DataSetCallback), ds);
      else
      {
         // implements the appending of text on textbox here
      }
   }

   private void txt_TextChanged(object sender, EventArgs e)
   {
      if (_yarn != null) _yarn.Abort();

      _yarn = new Thread(
         new Mate
         {
            LookupValuesDelegate = this.LookupValuesDelegate,
            LookupTextEventArgs =
            new LookupTextEventArgs
            {
               RowOffset = offset,
               Filter = txt.Text
            },
            PassDataSet = this.DataSetCallback
         }.DoWork);

      _yarn.Start();
   }
}


internal class Mate
{
   internal LookupTextEventArgs LookupTextEventArgs = null;

   internal LookupValuesDelegate LookupValuesDelegate = null;

   internal PassDataSet PassDataSet = null;


   object o = new object();
   internal void DoWork()
   {
      lock (o)
      {
         // the actual code that queries the database
         var ds = LookupValuesDelegate(LookupTextEventArgs);
         PassDataSet(ds);
      }
   }
}

ПРИМЕЧАНИЯ

Причиной отмены предыдущего потока при последовательном вводе пользователем кода является не только предотвращение добавления текста, но и отмена предыдущего сетевого обхода, чтобы программа не потребляла слишком много памяти в результате последовательной работы сети.

Я переживаю, если избегаю thread.Abort () в целом, программа может потреблять слишком много памяти.

вот код без потока. Abort (), используя счетчик:

internal delegate void PassDataSet(DataSet ds, int keyIndex);

public class AutoCompleteBox : UserControl
{
   [System.ComponentModel.Category("Data")]
   public LookupValuesDelegate LookupValuesDelegate { set; get; }

   static int _currentKeyIndex = 0;

   void DataSetCallback(DataSet ds, int keyIndex)
   {
      if (this.InvokeRequired)
         this.Invoke(new PassDataSet(DataSetCallback), ds, keyIndex);
      else
      {
         // ignore the returned DataSet
         if (keyIndex < _currentKeyIndex) return; 

         // implements the appending of text on textbox here...
      }
   }

   private void txt_TextChanged(object sender, EventArgs e)
   {
      Interlocked.Increment(ref _currentKeyIndex);

      var yarn = new Thread(
         new Mate
         {
            KeyIndex = _currentKeyIndex,
            LookupValuesDelegate = this.LookupValuesDelegate,
            LookupTextEventArgs =
            new LookupTextEventArgs
            {
               RowOffset = offset,
               Filter = txt.Text
            },
            PassDataSet = this.DataSetCallback
         }.DoWork);

      yarn.Start();
   }
}


internal class Mate
{
   internal int KeyIndex;
   internal LookupTextEventArgs LookupTextEventArgs = null;
   internal LookupValuesDelegate LookupValuesDelegate = null;
   internal PassDataSet PassDataSet = null;

   object o = new object();
   internal void DoWork()
   {
      lock (o)
      {
         // the actual code that queries the database
         var ds = LookupValuesDelegate(LookupTextEventArgs);
         PassDataSet(ds, KeyIndex);
      }
   }
}

Ответы [ 5 ]

31 голосов
/ 07 января 2009

Нет, это не является безопасным. Thread.Abort() достаточно отрывочен в лучшие времена, но в этом случае ваш элемент управления не имеет (хех) контроля над тем, что делается в обратном вызове делегата. Вы не знаете, в каком состоянии будет оставаться остальная часть приложения, и вполне можете оказаться в мире боли, когда придет время снова вызвать делегата.

Установите таймер. Подождите немного после изменения текста, прежде чем вызывать делегата. Затем подождите, пока он вернется, прежде чем вызывать его снова. Если это что медленно или пользователь набирает это быстро, то он, вероятно, все равно не ожидает автозаполнения.

Относительно вашего обновленного кода (Abort () - free):

Вы запускаете новую тему для (потенциально) каждого нажатия клавиши . Это не только снижает производительность, но и не нужно - если пользователь не делает паузу, он, вероятно, не ищет элемент управления, чтобы завершить ввод текста.

Я уже говорил об этом раньше, но P Папа сказал, что лучше :

Тебе было бы лучше просто реализовать таймер с одним выстрелом, возможно с тайм-аут на полсекунды и его сброс при каждом нажатии клавиши.

Подумайте об этом: быстрая машинистка может создать множество потоков до того, как первый обратный вызов автозаполнения сможет завершиться, даже при быстром соединении с быстрой базой данных. Но если вы откладываете выполнение запроса на короткий промежуток времени после того, как прошло последнее нажатие клавиши, тогда у вас больше шансов попасть в то место, где пользователь набрал все, что он хочет (или все, что он знает!), И это просто начинает ждать запуска автозаполнения. Поиграйте с задержкой - полсекунды может подойти для нетерпеливых машинисток, но если ваши пользователи немного расслаблены ... или ваша база данных немного медленнее ... тогда вы можете получить лучшие результаты с задержкой в ​​2-3 секунды или даже дольше. Однако наиболее важной частью этой техники является то, что вы reset the timer on every keystroke.

И если вы не ожидаете, что запросы к базе данных действительно зависнут , не пытайтесь разрешить несколько одновременных запросов. Если в данный момент выполняется запрос, дождитесь его завершения, прежде чем отправлять другой.

11 голосов
/ 07 января 2009

Там много предупреждений по всему сеть об использовании Thread.Abort. Я бы рекомендовал избегать этого, если это действительно не нужно, что в данном случае, я не думаю, что это так. Было бы лучше, если бы вы включили таймер с одним выстрелом, возможно, с тайм-аутом в полсекунды, и сбрасывали его при каждом нажатии клавиши. Таким образом, ваша дорогая операция произойдет только через полсекунды или более (или любой другой длины, которую вы выберете) бездействия пользователя.

4 голосов
/ 07 января 2009

Возможно, вы захотите взглянуть на Введение в программирование с использованием потоков C # - Эндрю Д. Биррелл. Он обрисовывает в общих чертах некоторые лучшие практики, связанные с многопоточностью в C #.

На странице 4 он говорит:

Когда вы смотрите на Пространство имен «System.Threading», вы будете (или должен) чувствовать себя испуганным диапазоном из вариантов, стоящих перед вами: «Монитор» или «Мьютекс»; «Ожидание» или «AutoResetEvent»; «Прервать» или «Прервать»? К счастью, есть простой ответ: используйте Оператор «lock», класс «Monitor», и метод «Прерывание». Те функции, которые я буду использовать для большинства остальная часть бумаги. На данный момент вы следует игнорировать остальную часть «System.Threading», хотя я буду обрисуйте его для вас в разделе 9.

3 голосов
/ 07 января 2009

Нет, я бы не стал вызывать Thread.Abort для вашего собственного кода. Вы хотите, чтобы ваш собственный фоновый поток завершился нормально и размотал свой стек естественным образом. Единственный раз, когда я мог бы рассмотреть возможность вызова Thread.Abort, - это сценарий, в котором мой код размещает внешний код в другом потоке (например, сценарий с плагином), и я действительно хочу прервать внешний код.

Вместо этого, в этом случае, вы можете рассмотреть возможность создания версий каждого фонового запроса. При обратном вызове игнорируйте ответы, которые являются «устаревшими», поскольку ответы сервера могут возвращаться в неправильном порядке. Я бы не стал слишком беспокоиться об отмене запроса, который уже был отправлен в базу данных. Если вы обнаружите, что ваша база данных не отвечает или перегружена слишком большим количеством запросов, рассмотрите возможность использования таймера, как предлагали другие.

0 голосов
/ 05 декабря 2011

Используйте Thread.Abort только в качестве крайней меры, когда вы выходите из приложения, и ЗНАЙТЕ, что все ВАЖНЫЕ ресурсы освобождены безопасно.

В противном случае, не делай этого. Это даже хуже, чем

try
{
//  do stuff
}
catch { } //  gulp the exception, don't do anything about it

сеть безопасности ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...