我创建了一个自定义自动完成控件,当用户按下一个键时,它会在另一个线程上查询数据库服务器(使用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);
}
}
}
答案 0 :(得分:31)
不,不是安全。 Thread.Abort()
在最好的时候是粗略的,但在这种情况下,你的控件没有(heh)控制委托回调中的操作。您不知道该应用程序的其余部分将处于什么状态,并且在再次致电该代表时可能会发现自己处于一个受伤的世界。
设置计时器。文本更改后稍等片刻,然后再调用委托。然后在再次调用它之前等待它返回。如果 慢,或者用户正在快速输入 ,那么他们可能不会期望自动完成。
您现在正在为(可能)每个按键启动一个新线程。这不仅会破坏性能,而且是不必要的 - 如果用户没有暂停,他们可能不会寻找控件来完成他们正在输入的内容。
我之前提到过,P Daddy said it better:
你最好只是实施 一次性计时器,可能是一个 半秒超时,并重置它 在每次击键时。
考虑一下:快速打字员可能会在第一次自动完成回调有机会完成之前创建一个线程分数,即使快速连接到快速数据库也是如此。但是如果你在最后一次击键后的短时间内延迟发出请求,那么你就有更好的机会击中用户输入他们想要的所有内容(或者他们所知道的所有内容!)并且< em>只是开始等待自动完成功能启动。延迟播放 - 半秒钟可能适合不耐烦的触摸打字员,但如果您的用户更放松......或者您的数据库是稍慢一点......那么你可以在延迟2-3秒甚至更长时间内获得更好的效果。但是,这项技术最重要的部分是你reset the timer on every keystroke
。
除非您希望数据库请求实际上挂起,否则不要试图允许多个并发请求。如果请求当前正在进行中,请等待它再完成。
答案 1 :(得分:11)
Thread.Abort
,There are many warnings all over the net。我会建议避免它,除非它真的需要,在这种情况下,我不认为它。你最好只实现一次性定时器,可能有半秒钟的超时,并在每次按键时重置它。这样,您的昂贵操作只会在用户不活动的半秒或更长时间(或您选择的任何长度)之后发生。
答案 2 :(得分:4)
你可能想看看An Introduction to Programming with C# Threads - Andrew D. Birrell。他概述了C#中有关线程的一些最佳实践。
第4页他说:
当你看着 “System.Threading”命名空间,你会的 (或应该)感到被范围吓到了 面对你的选择:“监视”或 “互斥”; “等待”或“AutoResetEvent”; “中断”或“中止”?幸好, 有一个简单的答案:使用 “锁定”声明,“监视”类, 和“中断”方法。那些是 我将用于大多数功能 本文的其余部分。现在,你 应该忽略剩下的 “System.Threading”,虽然我会 为你概述第9节。
答案 3 :(得分:3)
不,我会避免在你自己的代码上调用Thread.Abort。您希望自己的后台线程正常完成并自然地展开它的堆栈。我可能会考虑调用Thread.Abort的唯一一次是我的代码在另一个线程上托管外部代码(例如插件场景),我真的想要中止外部代码。
相反,在这种情况下,您可以考虑简单地对每个后台请求进行版本控制。在回调中,忽略“过时”的响应,因为服务器响应可能以错误的顺序返回。我不会过分担心中止已经发送到数据库的请求。如果您发现您的数据库没有响应或被太多请求所淹没,那么请考虑使用其他人建议的计时器。
答案 4 :(得分:0)
仅在退出应用程序时使用Thread.Abort
作为最后的手段,并且知道所有重要资源都是安全释放的。
否则,不要这样做。那就更糟了
try
{
// do stuff
}
catch { } // gulp the exception, don't do anything about it
安全网......