Подтвердить что ты не робот

Является ли этот thread.abort() нормальным и безопасным?

Я создал настраиваемый элемент автозаполнения, когда пользователь нажимает клавишу, он запрашивает сервер базы данных (используя 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);
      }
   }
}
4b9b3361

Ответ 1

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

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

Что касается вашего обновленного кода (Abort() - бесплатно):

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

Я коснулся этого раньше, но P Daddy сказал это лучше:

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

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

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

Ответ 2

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

Ответ 3

Возможно, вам стоит взглянуть на Введение в программирование с помощью потоков С# - Andrew D. Birrell. Он описывает некоторые из лучших практик, связанных с потоками в С#.

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

Когда вы смотрите на В пространстве имен "System.Threading" вы (или должен) чувствовать себя ослепленным диапазоном вариантов, стоящих перед вами: "Монитор" или "Мьютекс"; "Подождите" или "Авторешетка"; "Прерывание" или "Отмена"? К счастью, theres простой ответ: используйте "блокировка", класс "Монитор", и метод "Прерывание". Это функции, которые я использую для большинства остальная часть бумаги. Пока вы следует игнорировать остальную часть "System.Threading", хотя Ill опишите это для вас в разделе 9.

Ответ 4

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

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

Ответ 5

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

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

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

защитная сетка...