¿Cómo ejecuto un simple código en un nuevo hilo?

340

Tengo un poco de código que necesito ejecutar en un hilo diferente al de la GUI, ya que actualmente hace que el formulario se congele mientras se ejecuta el código (aproximadamente 10 segundos).

Suponga que nunca he creado un nuevo hilo antes; ¿Cuál es un ejemplo simple / básico de cómo hacer esto en C # y usar .NET Framework 2.0 o posterior?

p.campbell
fuente
2
La mayoría de las respuestas aquí eran buenas en ese momento, pero las mejoras en .NET Framework 4.0 simplifican las cosas. Puede usar el método Task.Run (), como se describe en esta respuesta: stackoverflow.com/a/31778592/1633949
Richard II

Respuestas:

336

Un buen lugar para comenzar a leer es Joe Albahari .

Si desea crear su propio hilo, esto es lo más simple posible:

using System.Threading;
new Thread(() => 
{
    Thread.CurrentThread.IsBackground = true; 
    /* run your code here */ 
    Console.WriteLine("Hello, world"); 
}).Start();
Ed Power
fuente
@EdPower, ¿esto se aplica solo a Winforms ... o funcionará en Web Forms ...?
MethodMan
@MethodMan: sí, funcionará en formularios web. Comience aquí:
Ed Power
99
Tenga cuidado al establecer IsBackgrounden verdadero. Probablemente no hace lo que crees que hace. Lo que hace es configurar si el subproceso se eliminará cuando todos los subprocesos en primer plano hayan muerto o si el subproceso mantendrá viva la aplicación. Si no desea que su hilo termine a mitad de la ejecución, no lo configure IsBackgrounden verdadero.
Zero3
10
@EdPower ¡Creo que debes tener cuidado de cualquier manera! Un ejemplo de una tarea que probablemente no desee finalizar a mitad de la ejecución es aquella que guarda datos en el disco. Pero claro, si su tarea es adecuada para la finalización en cualquier momento, la bandera está bien. Lo que quiero decir es que uno debe tener cuidado al usar la bandera , ya que no describió su propósito, y su denominación fácilmente podría llevarlo a creer que hace algo más de lo que realmente hace.
Zero3
3
con .NET Framework 4.0+ simplemente use Task.Run (), como se describe en esta respuesta: stackoverflow.com/a/31778592/1633949
Richard II
193

BackgroundWorker Parece ser la mejor opción para ti.

Aquí está mi ejemplo mínimo. Después de hacer clic en el botón, el trabajador en segundo plano comenzará a trabajar en el subproceso en segundo plano y también informará de su progreso simultáneamente. También informará después de que se complete el trabajo.

using System.ComponentModel;
...
    private void button1_Click(object sender, EventArgs e)
    {
        BackgroundWorker bw = new BackgroundWorker();

        // this allows our worker to report progress during work
        bw.WorkerReportsProgress = true;

        // what to do in the background thread
        bw.DoWork += new DoWorkEventHandler(
        delegate(object o, DoWorkEventArgs args)
        {
            BackgroundWorker b = o as BackgroundWorker;

            // do some simple processing for 10 seconds
            for (int i = 1; i <= 10; i++)
            {
                // report the progress in percent
                b.ReportProgress(i * 10);
                Thread.Sleep(1000);
            }

        });

        // what to do when progress changed (update the progress bar for example)
        bw.ProgressChanged += new ProgressChangedEventHandler(
        delegate(object o, ProgressChangedEventArgs args)
        {
            label1.Text = string.Format("{0}% Completed", args.ProgressPercentage);
        });

        // what to do when worker completes its task (notify the user)
        bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
        delegate(object o, RunWorkerCompletedEventArgs args)
        {
            label1.Text = "Finished!";
        });

        bw.RunWorkerAsync();
    }

Nota:

  • Puse todo en un solo método usando el método anónimo de C # por simplicidad, pero siempre puede extraerlos a diferentes métodos.
  • Es seguro actualizar la GUI ProgressChangedo los RunWorkerCompletedcontroladores. Sin embargo, la actualización de la GUI DoWork causará InvalidOperationException.
Gant
fuente
27
Usando System.ComponentModel; (puede evitar que las personas
realicen
@Gant Gracias por el código de muestra. Cuando probé su código, el evento ProgressChanged no se activaba. Sin embargo, cuando probé la respuesta aceptada similar aquí [ stackoverflow.com/questions/23112676/… funcionó.
Martin
1
@sooprise Ctrl +. te ayuda en estas situaciones! (Suponiendo que esté usando Visual Studio)
SepehrM
100

El ThreadPool.QueueUserWorkItem es bastante ideal para algo sencillo. La única advertencia es acceder a un control desde el otro hilo.

System.Threading.ThreadPool.QueueUserWorkItem(delegate {
    DoSomethingThatDoesntInvolveAControl();
}, null);
Mark Brackett
fuente
También mi favorito. Una línea rápida para el cruce . Lo tengo en marcación rápida (fragmento). Funciona muy bien con controles. Solo necesita saber cómo usar Invoke e InvokeRequired .
Bitterblue
1
+1 Tenga en cuenta que el delegado también le permite ajustar parámetros perfectamente.
Will Bickford
cómo usar ThreadPool.QueueUserWorkItem con la función de devolución de llamada significa que cuando el trabajo esté hecho, la devolución de llamada nos notificará.
Mou
76

Rápido y sucio, pero funcionará:

Usando en la parte superior:

using System.Threading;

código simple:

static void Main( string[] args )
{
    Thread t = new Thread( NewThread );
    t.Start();
}

static void NewThread()
{
    //code goes here
}

Acabo de lanzar esto en una nueva aplicación de consola para un ejemplo

FallenAvatar
fuente
8
Recuerde que los hilos marcados como IsBackground no son terminados automáticamente por Runtime. Esto requiere la gestión de hilos por parte de la aplicación. Si deja el hilo marcado como no en segundo plano, después de la ejecución de los hilos, el hilo termina por usted.
CmdrTallen
14
@CmdrTallen: Eso no está del todo bien. Un hilo marcado IsBackground = true medios que el hilo no se detendrá el proceso de salir es decir, un proceso saldrá cuando todas las roscas con IsBackground = false han salido
Phil Devaney
66

Aquí hay otra opción:

Task.Run(()=>{
//Here is a new thread
});
Bob Esponja Camarada
fuente
1
si crea un hilo de esta manera, ¿cómo podría detener este hilo del hilo de la interfaz de usuario?
slayernoah
1
@slayernoah, puede pasar un CancellationTokenSource como parámetro y configurar el token de cancelación fuera del hilo: msdn.microsoft.com/en-us/library/dd997364(v=vs.110).aspx
Spongebob Comrade
77
Este código no le garantizará tener un nuevo hilo. La decisión depende de la implementación actual del ThreadPool que está utilizando. Ver como ejemplo stackoverflow.com/questions/13570579/…
Giulio Caccin
3
@GiulioCaccin Es posible que ThreadPool elija un hilo existente de su grupo, pero definitivamente es un hilo diferente.
Bob Esponja camarada
40

Intenta usar la clase BackgroundWorker . Le da delegados para saber qué ejecutar y recibir una notificación cuando haya terminado el trabajo. Hay un ejemplo en la página de MSDN que he vinculado.

Andy
fuente
66
Ciertamente puede hacer esto con la clase Thread, pero BackgroundWorker le brinda métodos para completar el hilo y el informe de progreso que de lo contrario tendría que descubrir cómo usarlo usted mismo. ¡No olvides que debes usar Invoke para hablar con la interfaz de usuario!
Robert Rossney
1
Tenga en cuenta: BackgroundWorker tiene algunas limitaciones sutiles, pero para el común "Quiero irme y hacer algo y que mi forma se mantenga receptiva" es fantástico.
Merus
10
@ Merus, ¿podría ampliar cuáles son esas limitaciones sutiles?
Christian Hudon
14

Si quieres obtener un valor:

var someValue;

Thread thread = new Thread(delegate()
            {                 
                //Do somthing and set your value
                someValue = "Hello World";
            });

thread.Start();

while (thread.IsAlive)
  Application.DoEvents();
algo
fuente
6

Ponga ese código en una función (el código que no se puede ejecutar en el mismo hilo que la GUI), y para activar la ejecución de ese código coloque lo siguiente.

Thread myThread= new Thread(nameOfFunction);

workerThread.Start();

Llamar a la función de inicio en el objeto de subproceso provocará la ejecución de su llamada de función en un nuevo subproceso.

Barón Rojo
fuente
3
@ user50612 ¿De qué estás hablando? El nuevo hilo se ejecutará nameOfFunctionen segundo plano, y no en el hilo GUI actual. La IsBackgroundpropiedad determina si el hilo mantendrá viva la aplicación o no: msdn.microsoft.com/en-us/library/…
Zero3
5

Aquí, cómo puede usar hilos con una barra de progreso, es solo para entender cómo funcionan los hilos, en la forma que hay tres botones de barra de progreso y 4:

 public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    Thread t, t2, t3;
    private void Form1_Load(object sender, EventArgs e)
    {

        CheckForIllegalCrossThreadCalls = false;

         t = new Thread(birinicBar); //evry thread workes with a new progressBar


         t2 = new Thread(ikinciBar);


         t3 = new Thread(ucuncuBar);

    }

    public void birinicBar() //to make progressBar work
    {
        for (int i = 0; i < 100; i++) {
            progressBar1.Value++;
            Thread.Sleep(100); // this progressBar gonna work faster
        }
    }

    public void ikinciBar()
    {
        for (int i = 0; i < 100; i++)
        {
            progressBar2.Value++;
            Thread.Sleep(200);
        }


    }

    public void ucuncuBar()
    {
        for (int i = 0; i < 100; i++)
        {
            progressBar3.Value++;
            Thread.Sleep(300);
        }
    }

    private void button1_Click(object sender, EventArgs e) //that button to start the threads
    {
        t.Start();
        t2.Start(); t3.Start();

    }

    private void button4_Click(object sender, EventArgs e)//that button to stup the threads with the progressBar
    {
        t.Suspend();
        t2.Suspend();
        t3.Suspend();
    }

    private void button2_Click(object sender, EventArgs e)// that is for contuniue after stuping
    {
        t.Resume();
        t2.Resume();
        t3.Resume();
    }

    private void button3_Click(object sender, EventArgs e) // finally with that button you can remove all of the threads
    {
        t.Abort();
        t2.Abort();
        t3.Abort();
    }
}
Ammar Alyasry
fuente
3
// following declaration of delegate ,,,
public delegate long GetEnergyUsageDelegate(DateTime lastRunTime, 
                                            DateTime procDateTime);

// following inside of some client method
GetEnergyUsageDelegate nrgDel = GetEnergyUsage;
IAsyncResult aR = nrgDel.BeginInvoke(lastRunTime, procDT, null, null);
while (!aR.IsCompleted) Thread.Sleep(500);
int usageCnt = nrgDel.EndInvoke(aR);

Charles su código (arriba) no es correcto. No necesita girar, esperar a que se complete. EndInvoke se bloqueará hasta que se señale WaitHandle.

Si desea bloquear hasta su finalización, simplemente necesita

nrgDel.EndInvoke(nrgDel.BeginInvoke(lastRuntime,procDT,null,null));

o alternativamente

ar.AsyncWaitHandle.WaitOne();

Pero, ¿cuál es el punto de emitir llamadas anyc si bloquea? También podría usar una llamada síncrona. Una mejor apuesta sería no bloquear y pasar una lambda para la limpieza:

nrgDel.BeginInvoke(lastRuntime,procDT,(ar)=> {ar.EndInvoke(ar);},null);

Una cosa a tener en cuenta es que debe llamar a EndInvoke. Mucha gente olvida esto y termina filtrando WaitHandle, ya que la mayoría de las implementaciones asíncronas lanzan waithandle en EndInvoke.

Matt Davison
fuente
2

Si va a utilizar el objeto Thread sin procesar, debe establecer IsBackground en true como mínimo y también debe establecer el modelo de Threading Apartment (probablemente STA).

public static void DoWork()
{
    // do some work
}

public static void StartWorker()
{
    Thread worker = new Thread(DoWork);
    worker.IsBackground = true;
    worker.SetApartmentState(System.Threading.ApartmentState.STA);
    worker.Start()
}

Recomendaría la clase BackgroundWorker si necesita interacción con la interfaz de usuario.

usuario50612
fuente
Tenga cuidado al establecer IsBackground en verdadero. Probablemente no hace lo que crees que hace. Lo que hace es configurar si el subproceso se eliminará cuando todos los subprocesos en primer plano hayan muerto o si el subproceso mantendrá viva la aplicación. Si no desea que su subproceso termine a mitad de la ejecución, no establezca IsBackground en verdadero.
Zero3
1

Otra opción, que utiliza delegados y el conjunto de subprocesos ...

suponiendo que 'GetEnergyUsage' es un método que toma un DateTime y otro DateTime como argumentos de entrada, y devuelve un Int ...

// following declaration of delegate ,,,
public delegate long GetEnergyUsageDelegate(DateTime lastRunTime, 
                                            DateTime procDateTime);

// following inside of some client method 
GetEnergyUsageDelegate nrgDel = GetEnergyUsage;                     
IAsyncResult aR = nrgDel.BeginInvoke(lastRunTime, procDT, null, null);
while (!aR.IsCompleted) Thread.Sleep(500);
int usageCnt = nrgDel.EndInvoke(aR);
Charles Bretana
fuente
1
Charles, ¿tu código es funcionalmente diferente int usageCnt = nrgDel.Invoke(lastRunTime, procDT, null, null);? Parece que suspende el hilo actual con el sueño de todos modos ... Pensé que no ayudaría nada con el congelamiento de la GUI si lo llamas en el hilo de la GUI
IgorK
Sí, BeginInvoke llama al delegado en otro subproceso del grupo de subprocesos ... Si usa Invoke, el delegado se llama sincrónicamente en el subproceso actual ... Pero tiene razón, el sueño está mal ... debe eliminar eso y recopilar los resultados utilizando una función de devolución de llamada. Voy a editar para mostrar eso
Charles Bretana
1

Hay muchas formas de ejecutar hilos separados en .Net, cada una tiene comportamientos diferentes. ¿Necesita continuar ejecutando el hilo después de que se cierre la GUI? ¿Necesita pasar información entre el hilo y la GUI? ¿El hilo necesita actualizar la GUI? ¿Debería el hilo hacer una tarea y luego cerrar, o debería continuar ejecutándose? Las respuestas a estas preguntas le dirán qué método usar.

Hay un buen artículo sobre el método asíncrono sobre el en el sitio web de Code Project que describe los diversos métodos y proporciona un código de muestra.

Tenga en cuenta que este artículo fue escrito antes de que el patrón asíncrono / espera y la Biblioteca de tareas paralelas se introdujeran en .NET.

Dour High Arch
fuente
Este es el tipo de información que estaba buscando, pero su respuesta es víctima de la rotura de enlaces.
Chris
Gracias por hacérmelo saber, @Chris; Enlace fijo.
Dour High Arch
0

Cómo: utilizar un hilo de fondo para buscar archivos

Debe tener mucho cuidado con el acceso desde otros hilos a cosas específicas de la GUI (es común para muchos kits de herramientas de la GUI). Si desea actualizar algo en la GUI desde el subproceso de procesamiento, marque esta respuesta que creo que es útil para WinForms. Para WPF, vea esto (muestra cómo tocar el componente en el método UpdateProgress () para que funcione desde otros hilos, pero en realidad no me gusta que no lo esté haciendo CheckAccess()antes de hacerlo a BeginInvoketravés de Dispathcer, vea y busque CheckAccess en él)

Estaba buscando un libro específico de .NET sobre subprocesos y encontré este (descargable gratis). Consulte http://www.albahari.com/threading/ para obtener más detalles al respecto.

Creo que encontrará lo que necesita para iniciar la ejecución como nuevo subproceso en las primeras 20 páginas y tiene muchas más (no estoy seguro acerca de los fragmentos específicos de la GUI, quiero decir estrictamente específicos para subprocesos). Me alegraría saber qué piensa la comunidad sobre este trabajo porque estoy leyendo este. Por ahora parecía bastante ordenado para mí (para mostrar métodos y tipos específicos de .NET para subprocesos). También cubre .NET 2.0 (y no el antiguo 1.1) lo que realmente aprecio.

IgorK
fuente
0

Recomiendo mirar la Biblioteca de subprocesos de poder de Jeff Richter y específicamente el IAsyncEnumerator. Eche un vistazo al video en el blog de Charlie Calvert donde Richter lo repasa para obtener una buena visión general.

No se desanime por el nombre porque hace que las tareas de programación asíncrona sean más fáciles de codificar.

Robert Paulson
fuente