我创建了一个自定义自动完成控件,当用户按下一个键时,它会在另一个线程上查询数据库服务器(使用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(),程序可能会占用太多内存.
这里是没有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); } } }
Shog9.. 31
不,这是不是安全的.Thread.Abort()
在最好的时候足够粗略,但在这种情况下,你的控件没有(heh)控制委托回调中的操作.您不知道该应用程序的其余部分将处于什么状态,并且在再次致电该代表时可能会发现自己处于一个受伤的世界.
设置一个计时器.文本更改后稍等片刻,然后再调用委托.然后在再次调用它之前等待它返回.如果它是缓慢的,或在用户键入的是快的话,他们可能不希望自动完成反正.
您现在正在为(可能)每个按键启动一个新线程.这不仅会破坏性能,而且是不必要的 - 如果用户没有暂停,他们可能不会寻找控件来完成他们正在输入的内容.
我之前提到过这个问题,但是P爸爸说的更好:
你最好只实现一次性定时器,可能有半秒钟的超时,并在每次按键时重置它.
想一想:快速打字员可能会在第一次自动完成回调有机会完成之前创建一个线程分数,即使快速连接到快速数据库也是如此.但是如果你在最后一次击键后的短时间内推迟发出请求,那么你就有更好的机会击中用户输入他们想要的所有内容(或者他们所知道的全部内容!)并且只是开始等待自动完成开始播放.延迟播放 - 半秒钟可能适合不耐烦的触摸打字员,但如果你的用户稍微放松一点......或者你的数据库有点慢......那么你可以在延迟2-3秒甚至更长时间内获得更好的效果.但是,这项技术最重要的部分就是你reset the timer on every keystroke
.
除非您希望数据库请求实际挂起,否则不要试图允许多个并发请求.如果请求当前正在进行中,请在创建另一个请求之前等待它完成.
不,这是不是安全的.Thread.Abort()
在最好的时候足够粗略,但在这种情况下,你的控件没有(heh)控制委托回调中的操作.您不知道该应用程序的其余部分将处于什么状态,并且在再次致电该代表时可能会发现自己处于一个受伤的世界.
设置一个计时器.文本更改后稍等片刻,然后再调用委托.然后在再次调用它之前等待它返回.如果它是缓慢的,或在用户键入的是快的话,他们可能不希望自动完成反正.
您现在正在为(可能)每个按键启动一个新线程.这不仅会破坏性能,而且是不必要的 - 如果用户没有暂停,他们可能不会寻找控件来完成他们正在输入的内容.
我之前提到过这个问题,但是P爸爸说的更好:
你最好只实现一次性定时器,可能有半秒钟的超时,并在每次按键时重置它.
想一想:快速打字员可能会在第一次自动完成回调有机会完成之前创建一个线程分数,即使快速连接到快速数据库也是如此.但是如果你在最后一次击键后的短时间内推迟发出请求,那么你就有更好的机会击中用户输入他们想要的所有内容(或者他们所知道的全部内容!)并且只是开始等待自动完成开始播放.延迟播放 - 半秒钟可能适合不耐烦的触摸打字员,但如果你的用户稍微放松一点......或者你的数据库有点慢......那么你可以在延迟2-3秒甚至更长时间内获得更好的效果.但是,这项技术最重要的部分就是你reset the timer on every keystroke
.
除非您希望数据库请求实际挂起,否则不要试图允许多个并发请求.如果请求当前正在进行中,请在创建另一个请求之前等待它完成.
目前 有 许多 的警告 所有在 网有关使用Thread.Abort
.我会建议避免它,除非它真的需要,在这种情况下,我不认为它.你最好只实现一次性定时器,可能有半秒钟的超时,并在每次按键时重置它.这样,您的昂贵操作只会在用户不活动的半秒或更长时间(或您选择的任何长度)之后发生.