这是thread.abort()正常和安全吗?
我创build了一个自定义的自动完成控件,当用户按下一个键时,在另一个线程上查询数据库服务器(使用Remoting)。 当用户input速度非常快时,程序必须取消先前执行的请求/线程。
我以前实现它作为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); } } }
笔记
当用户键入连续键时取消前一个线程的原因,不仅是为了防止文本的附加发生,而且是为了取消以前的networking往返,所以程序不会消耗太多的内存networking运营。
我担心,如果我总是避免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); } } }
不,这是不安全的。 Thread.Abort()
在最好的时候足够粗略,但在这种情况下,你的控件没有(heh)控制在委托callback中正在做什么。 你不知道什么状态的应用程序的其余部分将留下,并很可能发现自己处于一个受到伤害的世界时,再次打电话给代表。
设置一个计时器。 在调用委托之前稍等片刻,稍等片刻。 然后等待它再次调用它之前返回。 如果速度很慢,或者用户input的速度很快,那么他们可能不会期望自动完成。
关于你更新的(Abort() – free)代码:
您现在正在启动(可能) 每个按键的新线程。 这不仅会导致性能下降,而且也是不必要的 – 如果用户没有暂停,他们可能不会寻找控制来完成他们正在input的内容。
我早些时候谈到这个,但是爸爸说得好 :
只需实现一次性定时器,可能需要半秒的超时时间,并在每次击键时重置它。
考虑一下:快速打字员可能会在第一次自动完成callback有机会完成之前创build一个线程分数,即使快速连接到快速数据库也是如此。 但是如果你延迟了最后一次击键后很短的时间内提出请求,那么你有更好的机会击中用户input的所有他们想要的(或他们所知道的!)的甜蜜点开始等待自动完成function。延迟播放 – 半秒钟可能适合不耐烦的触摸打字员,但如果您的用户稍微放松一些…或者您的数据库有点慢…那么你可能会得到更好的结果,延迟2-3秒,甚至更长。 这种技术最重要的部分是,你reset the timer on every keystroke
。
除非您希望数据库请求实际挂起 ,否则不要试图允许多个并发请求。 如果请求正在进行中,请等待它完成后再进行另一个请求。
网上 有 很多关于使用Thread.Abort
警告 。 我build议避免它,除非真的需要,在这种情况下,我不这么认为。 只需实现一次性定时器,可能需要半秒的超时时间,并在每次击键时重置它。 这样,您的昂贵操作只会在用户不活动的半秒或更长时间(或者您select的任何长度)之后才会发生。
你可能想看一看C#线程编程入门 – Andrew D. Birrell。 他概述了围绕C#线程的一些最佳实践。
他在第4页上说:
当你看“System.Threading”命名空间时,你会(或应该)感到被你面对的select范围所迷惑:“Monitor”或“Mutex”; “等待”或“AutoResetEvent”; “中断”还是“中止”? 幸运的是,有一个简单的答案:使用“locking”语句,“监视器”类和“中断”方法。 这些是我将用于本文其余大部分内容的function。 现在,你应该忽略“System.Threading”的其余部分,尽pipe我会在第9节中概述它。
不,我会避免在自己的代码上调用Thread.Abort。 你想自己的后台线程正常完成,并自然地展开它的堆栈。 唯一的时间我可能会考虑调用Thread.Abort是在我的代码在另一个线程(如插件场景)托pipe外国代码的情况下,我真的想中止外国代码。
相反,在这种情况下,您可以考虑简单地版本化每个背景请求。 在callback中,忽略“过期”的响应,因为服务器响应可能以错误的顺序返回。 我不会太担心中止已经发送到数据库的请求。 如果你发现你的数据库没有响应,或者被太多的请求所淹没,那么也可以考虑像其他人一样使用定时器。
使用Thread.Abort
仅作为退出应用程序时的最后一Thread.Abort
措施,并且知道所有重要资源都已安全释放。
否则,不要这样做。 那更糟了
try { // do stuff } catch { } // gulp the exception, don't do anything about it
安全网