我创建了一个自定义自动完成控件,当用户按下一个键时,它将在另一个线程上查询数据库服务器(使用远程处理)。当用户快速键入时,程序必须取消先前执行的请求/线程。
我以前首先将其实现为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(),该程序可能会占用太多内存。
这是不带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()最好的时候是足够粗略的,但是在这种情况下,您的控件无法(委托)控制委托回调中的操作。您不知道该应用程序的其余部分将保留在什么状态,并且当需要再次致电该委托人时,很可能会陷入困境。
Thread.Abort()
设置一个计时器。文本更改后稍等片刻,然后再调用委托。然后等待它返回,然后再次调用它。如果它 是 缓慢的,或用户打字 是 快的话,他们可能不希望自动完成反正。
现在,您将为(可能) 每个按键 启动一个新线程。这不仅会降低性能,而且没有必要-如果用户没有暂停,他们很可能不在寻找该控件来完成输入的内容。
我之前提到过,但是P Daddy说的更好:
您最好只实现一个一键式定时器(可能会有一个半秒的超时),并在每次击键时将其重置。
想想看:即使是快速连接到快速数据库,快速打字员也可能在第一个自动完成回调有机会完成之前创建线程得分。但是,如果你们推迟,直到最后一次按键后的短时间内请求已过,那么你打的是甜蜜点,其中用户已键入了所有他们想要更好的机会(或所有他们知道!),并且是 刚 开始等待自动完成功能开始。延迟播放- 半秒可能适合不耐烦的触摸打字员,但是如果您的用户更放松…或者您的数据库更慢…那么您可能会延迟2-3秒甚至更长的时间来获得更好的结果。但是,此技术最重要的部分是您reset the timer on every keystroke。
reset the timer on every keystroke
并且除非您期望数据库请求实际 挂起 ,否则请不要试图允许多个并发请求。如果当前正在处理一个请求,请在另一个请求完成之前等待它完成。