【问题标题】:Is this thread.abort() normal and safe?这个 thread.abort() 正常且安全吗?
【发布时间】:2009-01-07 18:16:53
【问题描述】:

我创建了一个自定义自动完成控件,当用户按下一个键时,它会在另一个线程上查询数据库服务器(使用远程处理)。当用户打字非常快时,程序必须取消之前执行的请求/线程。

我之前先将它实现为 AsyncCallback,但我觉得它很麻烦,要遵循的内部规则太多(例如 AsyncResult、AsyncState、EndInvoke),而且您必须检测 BeginInvoke 对象的线程,因此您可以终止之前执行的线程。此外,如果我继续 AsyncCallback,那些 AsyncCallback 上没有任何方法可以正确终止先前执行的线程。

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(),程序可能会消耗太多内存。

这是没有 thread.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);
      }
   }
}

【问题讨论】:

  • 刚刚看到您的编辑...您实际上可能想提交另一个关于定时自动完成的问题;这本身就是一个有点棘手的主题。

标签: c# multithreading


【解决方案1】:

不,它安全。 Thread.Abort() 在最好的情况下已经足够粗略了,但是在这种情况下,您的控件无法(呵呵)控制委托回调中正在执行的操作。您不知道应用程序的其余部分将处于什么状态,并且当再次致电代表时,您很可能会发现自己处于一个受伤的世界。

设置一个计时器。在文本更改后稍等片刻,然后再调用委托。然后等待它返回,然后再次调用它。如果它那么慢,或者用户输入速度那么,那么他们可能不会期望自动完成。

关于您更新的(Abort()-free)代码:

您现在为(可能)每个按键 启动一个新线程。这不仅会降低性能,而且是不必要的——如果用户没有暂停,他们可能不是在寻找控件来完成他们正在输入的内容。

我之前提到过这个,但是P Daddy said it better

你最好只实施 一次性计时器,可能带有 半秒超时,并重置它 每次击键。

想一想:快速打字员可能会在第一次自动完成回调有机会完成之前创建许多线程,即使快速连接到快速数据库也是如此。但是,如果您将请求延迟到最后一次击键过去后的一小段时间,那么您就有更好的机会到达用户输入他们想要的所有内容(或他们知道的所有内容!)并且是 刚刚开始等待自动完成启动。玩延迟 - 半秒可能适合不耐烦的触摸打字员,但如果你的用户更放松一点......或者你的数据库慢一点……然后你可能会得到更好的结果,延迟 2-3 秒,甚至更长。不过,这项技术最重要的部分是您reset the timer on every keystroke

除非您预计数据库请求实际上会挂起,否则不要费心尝试允许多个并发请求。如果一个请求当前正在进行中,请等待它完成后再发出另一个请求。

【讨论】:

  • 我会考虑这个 -> 如果一个请求当前正在进行中,请等待它完成后再发出另一个请求。
【解决方案2】:

Therearemanywarningsall overthe net关于使用Thread.Abort。除非真的需要,否则我建议避免使用它,在这种情况下,我认为不需要。你最好只实现一个一次性计时器,可能有半秒的超时,并在每次击键时重置它。这样,您的昂贵操作只会在用户不活动半秒或更长时间(或您选择的任何长度)后发生。

【讨论】:

    【解决方案3】:

    您可能想看看An Introduction to Programming with C# Threads - Andrew D. Birrell。他概述了一些围绕 C# 线程的最佳实践。

    在第 4 页他说:

    当你看到 “System.Threading”命名空间,您将 (或应该)被范围吓倒 您面临的选择:“监控”或 “互斥体”; “等待”或“AutoResetEvent”; “中断”还是“中止”?幸运的是, 有一个简单的答案:使用 “lock”语句,“Monitor”类, 和“中断”方法。那些是 我将在大多数情况下使用的功能 论文的其余部分。目前,你 应该忽略其余的 “System.Threading”,虽然我会 第 9 节为您概述。

    【讨论】:

      【解决方案4】:

      不,我会避免在您自己的代码上调用 Thread.Abort。您希望自己的后台线程正常完成并自然展开其堆栈。我可能会考虑调用 Thread.Abort 的唯一一次是在我的代码在另一个线程上托管外来代码的情况下(例如插件场景),我真的想中止外来代码。

      相反,在这种情况下,您可以考虑简单地对每个后台请求进行版本控制。在回调中,忽略“过时”的响应,因为服务器响应可能以错误的顺序返回。我不会太担心中止已经发送到数据库的请求。如果您发现您的数据库没有响应或因请求过多而不堪重负,那么请考虑使用其他人建议的计时器。

      【讨论】:

        【解决方案5】:

        仅当您退出应用程序并知道所有重要资源都已安全释放时,才将Thread.Abort 用作最后的措施。

        否则,不要这样做。那就更惨了

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

        安全网...

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2010-10-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-03-11
          相关资源
          最近更新 更多