2009-09-26 26 views
5

estoy usando el siguiente código para hacer lo que estoy pidiendo:¿Cómo iniciar el hilo si se presiona el botón y detenerlo si se vuelve a presionar?

private delegate void CallerDelegate(object e); 
CallerDelegate caler = new CallerDelegate(MethodToCall); 

en el evento de clic de botón:

if (currBusyThrd != null && currBusyThrd.IsAlive) 
    { 
    currBusyThrd.Abort(); 
    } 
ThreadPool.SetMaxThreads(1, 1); 
//queue the work for thread processing 
ThreadPool.QueueUserWorkItem(new WaitCallback(WaitCallbackMethod)) 

Método "WaitCallbackMethod" es:

void WaitCallbackMethod(object stateInfo) 
    { 
    // argList : i put some argument in a list to use it in "MethodToCall" ... 
    BeginInvoke(caler,argList); 
    } 

y el método que estoy llamando por el hilo es:

void MethodToCall(object args) 
{ 
//Here I get the thread I'm calling to stop it when btn clicked again 
currBusyThrd = Thread.CurrentThread; 

// The rest of the code ... 
} 

Creo que esto está mal ... ¿Cómo hacerlo bien?

En realidad, la llamada será por TextBox_KeyUp ... así que cada vez que el usuario ingrese un carácter, el código se ejecutará nuevamente ... y BackgroundWorker no funcionó.

+0

así que estás iniciar y detener un hilo de marcha larga con cada pulsación de tecla otra? –

+0

sí, exactamente, eso es lo que trato de hacer – Dabbas

+0

@ AL0NE, he actualizado mi respuesta. Vea el método ExecuteThreadLogicConditionally(). –

Respuesta

9

Un problema con este enfoque es que es muy peligroso abortar arbitrariamente un hilo (en casi cualquier idioma). Hay demasiados problemas que pueden aparecer en los recursos no actualizados y en los bloqueos perdidos. Por lo general, es mejor establecer algún tipo de indicador para solicitar que el subproceso se aborte de forma segura o para olvidarse del hilo y dejar que se ejecute hasta su finalización.

Además, abortar un hilo en el ThreadPool es muy peligroso y no creo que sea una operación compatible. Los Threads en ThreadPool no son de su propiedad y abortarlos en frío tienen serias implicaciones para ThreadPool.

Aquí está la solución que tomaría.

private object m_lock = new object(); 
private bool m_isRunning = false; 
private bool m_isAbortRequested = false; 

public void OnButtonClick(object sender, EventArgs e) { 
    lock (m_lock) { 
    if (m_isRunning) { 
     m_isAbortRequested = true; 
    } else { 
     m_isAbortRequested = false; 
     m_isRunning = true; 
     ThreadPool.QueueUserWorkItem(BackgroundMethod); 
    } 
    } 
} 

private void BackgroundMethod() { 
    try { 
    DoRealWork(); 
    } finally { 
    lock (m_lock) { 
     m_isRunning = false; 
    } 
    } 
} 

private void DoRealWork() { 
    ... 
    if (m_isAbortRequested) { 
    return; 
    } 
} 
+0

Quiero detener el hilo de trabajo porque hace un trabajo de larga duración y lo hará cada vez que el usuario toque el btn. – Dabbas

+1

@ AL0NE: parece que es posible que desee desactivar el botón mientras el hilo está haciendo su trabajo para evitar que el trabajo se reinicie en paralelo? –

+1

@JaredPar, ¿no es necesario que el indicador m_isAbortRequested sea volátil o esté sincronizado, ya que está establecido por el subproceso de la interfaz de usuario y verificado por el subproceso ThreadPool? –

3

Sí, esto está muy mal. Nunca intente controlar manualmente un hilo ThreadPool. Si necesita este tipo de control, debe usar su propio objeto Thread. Además, Abort() no es la forma recomendada de finalizar un hilo; debe tener un control volatile bool en su formulario que el código en MethodToCall comprueba en varios puntos y sale con gracia cuando es true. Si bien puede usar el mismo enfoque con el ThreadPool, el hecho de que deba poder cancelar parece indicar que el proceso es de larga duración, o al menos tiene potencial. El ThreadPool no debe usarse para procesos de larga ejecución.

Por ejemplo ...

private volatile bool stopThread = false; 
private Thread workThread; 

private void StartThread() 
{ 
    if(workThread == null) 
    { 
     stopThread = false; 
     workThread = new Thread(new ThreadStart(MethodToCall)); 

     workThread.Start(); 
    } 
} 

private void StopThread() 
{ 
    if(workThread != null) 
    { 
     stopThread = true; 

     workThread.Join(); // This makes the code here pause until the Thread exits. 

     workThread = null; 
    } 
} 

Luego, en MethodToCall, simplemente marque el booleano stopThread a intervalos frecuentes y realizar cualquier trabajo de limpieza que lo que necesita hacer y salir del método. Por ejemplo ...

private void MethodToCall() 
{ 
    // do some work here and get to a logical stopping point 

    if(stopThread) 
    { 
     // clean up your work 

     return; 
    } 

    // do some more work and get to another stopping point 

    if(stopThread) 
    { 
     // clean up your work 

     return; 
    } 
} 

Y simplemente repita ese patrón.

2

Para situaciones en las que un hilo necesita 'señal' otro hilo para hacer algo, por lo general utilizan un System.Threading.ManualResetEvent para señalar el subproceso secundario para detener, así:

private volatile bool _threadRunning = false; 
private ManualResetEvent _signal = new ManualResetEvent(false); 
private Thread _thread; 
private void OnButtonClick(object sender, EventArgs e) 
{ 
    if (!_threadRunning) { 
     // Reset the 'signal' event. 
     _signal.Reset(); 
     // Build your thread parameter here. 
     object param = ; 
     // Create the thread. 
     _thread = new Thread(ExecuteThreadLogicConditionally(param)); 
     // Make sure the thread shuts down automatically when UI closes 
     _thread.IsBackground = true; 
     // Start the thread. 
     _thread.Start(); 
     // Prevent another thread from being started. 
     _threadRunning = true; 
    } else { 
     // Signal the thread to stop. 
     _signal.Set(); 
     // DO NOT JOIN THE THREAD HERE! If the thread takes a while 
     // to exit, then your UI will be frozen until it does. Just 
     // set the signal and move on. 
    } 
} 
// If the thread is intended to execute its logic over and over until 
// stopped, use this callback. 
private void ExecuteThreadLogicUntilStopped(object param) 
{ 
    // Use a while loop to prevent the thread from exiting too early. 
    while (!_signal.WaitOne(0)) { 
     // Put your thread logic here... 
    } 
    // Set the flag so anther thread can be started. 
    _threadRunning = false; 
} 
// If the thread logic is to be executed once and then wait to be 
// shutdown, use this callback. 
private void ExecuteThreadLogicOnce(object param) 
{ 
    // Put your thread logic here... 
    // 
    // Now wait for signal to stop. 
    _signal.WaitOne(); 
    // Set the flag so another thread can be started. 
    _threadRunning = false; 
} 
// If the thread needs to be stopped at any point along the way, use 
// this callback. The key here is to 'sprinkle' checks of the 'signal' 
// to see if the thread should stop prematurely. 
private void ExecuteThreadLogicConditionally(object param) 
{ 
    if (_signal.WaitOne(0)) { _threadRunning = false; return; } 
    // Execute small chunk of logic here... 
    if (_signal.WaitOne(0)) { _threadRunning = false; return; } 
    // Execute another small chuck of logic here... 
    if (_signal.WaitOne(0)) { _threadRunning = false; return; } 
    // Continue this pattern through the method. 
} 

Tenga en cuenta que este solución no usa ThreadPool en absoluto. Podría hacerse fácilmente para hacerlo.Y como sugerencia, no me equivocaría con la función SetMaxThreads() en ThreadPool. Solo deja que ThreadPool haga su trabajo. Ha sido diseñado para ser óptimo para la forma en que lo usa.

+0

gracias, pero la mayoría de las soluciones usan "while (NotAskingToStopTheThread)" pero en mi pregunta mencioné que no quiero que el subproceso complete su tarea ... Quiero detenerlo antes de eso ... Y cuando se usa mientras No puedo hacer eso. – Dabbas

+0

La única forma de hacer lo que está pidiendo es verificar periódicamente un indicador, o el evento ManualResetEvent en mi ejemplo, para ver si el hilo debe detenerse. En otras palabras, necesita 'rociar' estas verificaciones en puntos lógicos en el código ejecutado por su hilo. Actualizaré mi respuesta para mostrar un ejemplo. –

+0

Gracias por su ayuda @Matt – Dabbas

1

Prueba este código ..

using System; 
using System.Linq; 
using System.Windows.Forms; 
using System.Threading; 
using System.Diagnostics; 

namespace WindowsFormsApplication1 
{ 
    public partial class Form1 : Form 
    { 
     Thread workerThread = null; 
     ManualResetEvent threadInterrupt = new ManualResetEvent(false); 

     public Form1() 
     { 
      InitializeComponent(); 
     } 

     private void button1_Click(object sender, EventArgs e) 
     { 
      if (this.workerThread == null) 
      { 
       this.threadInterrupt.Reset(); 
       this.workerThread = new Thread(() => 
       { 
        int i = 0; 
        while (!this.threadInterrupt.WaitOne(0)) 
        { 
         Debug.Print("put your code in here while worker thread running.. " + i.ToString()); 
         Thread.Sleep(100); 
         i++; 
        } 
        this.workerThread = null; 
        // worker thread finished in here.. 
       }); 
       this.workerThread.IsBackground = true; 
       // start worker thread in here 
       this.workerThread.Start(); 
      } 
      else 
      { 
       // stop worker thread in here 
       threadInterrupt.Set(); 
      } 
     } 

    } 
} 
Cuestiones relacionadas