c# - visual - ¿Es este thread.abort() normal y seguro?
threadstart c# (5)
Es posible que desee echar un vistazo a Una introducción a la programación con hilos C # - Andrew D. Birrell. Él describe algunas de las mejores prácticas que rodean el roscado en C #.
En la página 4 dice:
Cuando miras el espacio de nombres "System.Threading", te sentirás (o deberás) intimidado por el rango de opciones que tienes frente a ti: "Monitor" o "Mutex"; "Esperar" o "AutoResetEvent"; "Interrumpir" o "Abortar"? Afortunadamente, hay una respuesta simple: use la instrucción "lock", la clase "Monitor" y el método "Interrupt". Esas son las características que utilizaré para la mayor parte del resto del artículo. Por ahora, debe ignorar el resto de "System.Threading", aunque lo resumiré para usted en la sección 9.
Creé un control de autocompletar personalizado, cuando el usuario presiona una tecla, consulta el servidor de la base de datos (usando Remoting) en otro hilo. Cuando el usuario escribe muy rápido, el programa debe cancelar la solicitud / subproceso que se ejecutó previamente.
Anteriormente lo implementé como AsyncCallback, pero me resulta engorroso, demasiadas reglas de la casa a seguir (por ejemplo, AsyncResult, AsyncState, EndInvoke) además de que tienes que detectar el hilo del objeto BeginInvoke, para que puedas terminar el hilo que se estaba ejecutando anteriormente . Además, si continúo el AsyncCallback, no hay ningún método en esos AsyncCallbacks que pueda terminar adecuadamente el hilo que se ejecuta previamente.
EndInvoke no puede finalizar el hilo, aún así completaría la operación del hilo terminado. Todavía terminaría usando Abort () en el hilo.
Así que decidí implementarlo solo con el enfoque Thread puro, sin el AsyncCallback. ¿Es este thread.abort () normal y seguro para ti?
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);
}
}
}
NOTAS
La razón para cancelar el hilo anterior cuando el usuario ingresa las teclas en sucesión no es solo para evitar que se anexe texto, sino también para cancelar el viaje de ida y vuelta de la red anterior, por lo que el programa no consumirá demasiada memoria como resultado de sucesivas operación de red
Me preocupa si evito el hilo. Aborto () en conjunto, el programa podría consumir demasiada memoria.
aquí está el código sin el hilo. Aborto (), usando un contador:
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);
}
}
}
No, evitaría llamar a Thread.Abort en tu propio código. Desea que su propio hilo de fondo se complete normalmente y desenrolle su pila de forma natural. El único momento en que podría considerar llamar a Thread.Abort es en un escenario en el que mi código aloja código extraño en otro hilo (como un escenario de complemento) y realmente quiero abortar el código foráneo.
En cambio, en este caso, puede considerar simplemente versionar cada solicitud de fondo. En la devolución de llamada, ignore las respuestas que están "desactualizadas" ya que las respuestas del servidor pueden regresar en el orden incorrecto. No me preocuparía demasiado abortar una solicitud que ya se envió a la base de datos. Si encuentra que su base de datos no responde o está siendo abrumada por demasiadas solicitudes, entonces considere también usar un temporizador como otros han sugerido.
No, no es seguro. Thread.Abort()
es lo suficientemente incompleto en el mejor de los casos, pero en este caso su control no tiene (heh) control sobre lo que se está haciendo en la devolución de llamada del delegado. No sabe en qué estado quedará el resto de la aplicación, y puede encontrarse en un mundo de dolor cuando llegue el momento de volver a llamar al delegado.
Configura un temporizador. Espere un poco después de que el texto cambie antes de llamar al delegado. Luego espera a que vuelva antes de volver a llamarlo. Si es tan lento, o el usuario está escribiendo tan rápido, entonces probablemente no esperen que se complete automáticamente.
En cuanto a su código actualizado (Abortar () - gratis):
Ahora está lanzando un nuevo hilo para (potencialmente) cada pulsación de tecla . Esto no solo va a acabar con el rendimiento, sino que es innecesario: si el usuario no hace una pausa, probablemente no esté buscando el control para completar lo que está escribiendo.
He tocado esto antes, pero P Daddy lo dijo mejor :
Sería mejor simplemente implementar un temporizador de una sola toma, con un tiempo de espera de medio segundo, y reiniciarlo con cada pulsación de tecla.
Piénselo: un mecanógrafo rápido podría crear una veintena de subprocesos antes de que la primera devolución de llamada con autocompletar haya tenido la oportunidad de finalizar, incluso con una conexión rápida a una base de datos rápida. Pero si demora la solicitud hasta que haya transcurrido un corto período de tiempo después de que haya transcurrido la última pulsación de tecla, tendrá más posibilidades de alcanzar ese punto óptimo en el que el usuario ha tipeado todo lo que desea (¡o todo lo que saben!) comenzando a esperar a que se complete el autocompletado. Juegue con la demora: un medio segundo podría ser apropiado para los mecanógrafos táctiles impacientes, pero si sus usuarios están un poco más relajados ... o su base de datos es un poco más lenta ... entonces puede obtener mejores resultados con un retraso de 2-3 segundos, o incluso más. Sin embargo, la parte más importante de esta técnica es que reset the timer on every keystroke
.
Y a menos que espere que las solicitudes de la base de datos se cuelguen realmente, no se moleste en intentar permitir múltiples solicitudes simultáneas. Si una solicitud está actualmente en curso, espere a que se complete antes de hacer otra.
Utilice Thread.Abort
solo como una medida de último recurso cuando salga de la aplicación y SABE que todos los recursos IMPORTANTES se liberan de manera segura.
De lo contrario, no lo hagas. Es aún peor entonces
try
{
// do stuff
}
catch { } // gulp the exception, don''t do anything about it
red de seguridad...
There many warnings toda la red sobre el uso de Thread.Abort
. Recomendaría evitarlo a menos que sea realmente necesario, que en este caso, no creo que lo sea. Sería mejor simplemente implementar un temporizador de una sola toma, con un tiempo de espera de medio segundo, y reiniciarlo con cada pulsación de tecla. De esta forma, su costosa operación solo ocurrirá después de medio segundo o más (o la duración que elija) de la inactividad del usuario.