Como executo um pequeno pedaço de código em um novo thread?


340

Eu tenho um pouco de código que preciso executar em um thread diferente da GUI, pois atualmente faz com que o formulário congele enquanto o código é executado (10 segundos ou mais).

Suponha que eu nunca criei um novo segmento antes; o que é um exemplo simples / básico de como fazer isso em C # e usando o .NET Framework 2.0 ou posterior?


2
A maioria das respostas aqui eram boas na época, mas as melhorias no .NET Framework 4.0 simplificam as coisas. Você pode usar o método Task.Run (), conforme descrito nesta resposta: stackoverflow.com/a/31778592/1633949
Richard II

Respostas:


336

Um bom lugar para começar a ler é Joe Albahari .

Se você deseja criar seu próprio encadeamento, é o mais simples possível:

using System.Threading;
new Thread(() => 
{
    Thread.CurrentThread.IsBackground = true; 
    /* run your code here */ 
    Console.WriteLine("Hello, world"); 
}).Start();

@ EdPower, isso se aplica somente ao Winforms .. ou funcionará em Web Forms ..?
MethodMan

@MethodMan - Sim, funcionará em Web Forms. Comece aqui:
Ed Power

9
Cuidado ao definir IsBackgroundcomo true. Provavelmente não faz o que você pensa que faz. O que ele faz é configurar se o encadeamento será eliminado quando todos os encadeamentos em primeiro plano morrerem ou se o encadeamento manterá o aplicativo ativo. Se você não deseja que seu encadeamento seja finalizado no meio da execução, não configure IsBackgroundcomo true.
Zero3 22/09

10
@ EdPower Eu acho que você deve ter cuidado de qualquer maneira! Um exemplo de tarefa que você provavelmente não deseja finalizar no meio da execução é o que salva dados no disco. Mas com certeza, se sua tarefa for adequada para rescisão a qualquer momento, o sinalizador estará correto. Meu argumento era que é preciso ter cuidado com o uso da bandeira , pois você não descreveu seu objetivo, e sua nomeação poderia facilmente levar a acreditar que ele faz outra coisa além do que realmente faz.
Zero3 23/09

3
com o .NET Framework 4.0+, basta usar Task.Run (), conforme descrito nesta resposta: stackoverflow.com/a/31778592/1633949
Richard II

193

BackgroundWorker parece ser a melhor escolha para você.

Aqui está o meu exemplo mínimo. Depois de clicar no botão, o trabalhador em segundo plano começará a trabalhar no encadeamento em segundo plano e também relatará seu progresso simultaneamente. Também será relatado após a conclusão do trabalho.

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:

  • Coloquei tudo no método único usando o método anônimo do C # para simplificar, mas você sempre pode utilizá-los em métodos diferentes.
  • É seguro atualizar a GUI dentro ProgressChangedou nos RunWorkerCompletedmanipuladores. No entanto, atualizar a GUI de DoWork causará InvalidOperationException.

27
Using System.ComponentModel; (pode salvar as pessoas de fazer uma pesquisa no Google, obrigado por este exemplo de código útil) +1
sooprise

@ Gant Obrigado pelo código de exemplo. Quando tentei seu código, o evento ProgressChanged não estava sendo acionado. No entanto, quando tentei a resposta aceita semelhante aqui [ stackoverflow.com/questions/23112676/… funcionou.
Martin

11
@sooprise Ctrl +. ajuda você nessas situações! (Supondo que você estiver usando Visual Studio)
SepehrM

100

O ThreadPool.QueueUserWorkItem é bastante ideal para algo simples. A única ressalva é acessar um controle do outro segmento.

System.Threading.ThreadPool.QueueUserWorkItem(delegate {
    DoSomethingThatDoesntInvolveAControl();
}, null);

Também o meu favorito. Uma linha rápida para o cruzamento . Tenho na discagem rápida (trecho). Funciona muito bem com controles. Você só precisa saber como usar Invoke e InvokeRequired .
Bitterblue

11
+1 Observe que o delegado também permite agrupar os parâmetros com precisão.
que você precisa

como usar ThreadPool.QueueUserWorkItem com função de retorno de chamada significa que quando o trabalho for concluído, a chamada de retorno nos notificará.
Mou

76

Rápido e sujo, mas funcionará:

Usando no topo:

using System.Threading;

código simples:

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

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

Acabei de jogar isso em um novo aplicativo de console para um exemplo


8
Lembre-se de threads sinalizados IsBackground não são automaticamente encerrados pelo tempo de execução. Isso requer gerenciamento de threads pelo aplicativo. Se você deixar o encadeamento marcado como não em segundo plano, após a execução do encadeamento, o encadeamento será encerrado para você.
CmdrTallen

14
@CmdrTallen: Isso não está certo. Um fio marcado IsBackground = true significa que thread não irá parar o processo de sair ou seja, um processo será encerrado quando todos os segmentos com IsBackground = false ter saído
Phil Devaney

66

Aqui está outra opção:

Task.Run(()=>{
//Here is a new thread
});

11
se você criar um thread dessa maneira, como você o interromperia no thread da interface do usuário?
Slayernoah

11
@slayernoah, você pode passar um CancellationTokenSource como um parâmetro para ele e definir o token de cancelamento fora do thread: msdn.microsoft.com/en-us/library/dd997364(v=vs.110).aspx
Spongebob Camarada

7
Este código não garante que você tenha um novo thread. A decisão depende da implementação atual do ThreadPool que você está usando. Veja como exemplo stackoverflow.com/questions/13570579/…
Giulio Caccin 5/17

3
@ GiulioCaccin É possível ao ThreadPool escolher um thread existente em seu pool, mas é definitivamente um thread diferente.
Camarada Bob Esponja

40

Tente usar a classe BackgroundWorker . Você fornece delegados para o que executar e para ser notificado quando o trabalho terminar. Há um exemplo na página do MSDN ao qual vinculei.


6
Você certamente pode fazer isso com a classe Thread, mas o BackgroundWorker fornece métodos para conclusão de threads e relatórios de progresso que, caso contrário, você teria que descobrir como usar a si mesmo. Não esqueça que você precisa usar o Invoke para conversar com a interface do usuário!
Robert Rossney 12/12/08

11
Esteja avisado: o BackgroundWorker tem algumas limitações sutis, mas para o comum "Eu quero ir embora e fazer alguma coisa e manter minha forma responsiva", é fantástico.
Merus

10
@Merus, você poderia expandir quais são essas limitações sutis?
Christian Hudon 18/09/12

14

Se você deseja obter um valor:

var someValue;

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

thread.Start();

while (thread.IsAlive)
  Application.DoEvents();

6

Coloque esse código em uma função (o código que não pode ser executado no mesmo encadeamento que a GUI) e, para acionar a execução desse código, coloque o seguinte.

Thread myThread= new Thread(nameOfFunction);

workerThread.Start();

Chamar a função de início no objeto de encadeamento causará a execução da sua chamada de função em um novo encadeamento.


3
@ user50612 Do que você está falando? O novo thread será executado nameOfFunctionem segundo plano, e não no thread da GUI atual. A IsBackgroundpropriedade determina se o thread manterá o aplicativo ativo ou não: msdn.microsoft.com/en-us/library/…
Zero3

5

Aqui, como usar threads com um progressBar, é apenas para sublinhar como os threads funcionam, no formulário há três botões progressBar e 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();
    }
}

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, seu código (acima) não está correto. Você não precisa girar a espera para concluir. EndInvoke bloqueará até que o WaitHandle seja sinalizado.

Se você deseja bloquear até a conclusão, basta

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

ou alternativamente

ar.AsyncWaitHandle.WaitOne();

Mas qual é o sentido de emitir chamadas anyc se você bloquear? Você também pode usar uma chamada síncrona. Uma aposta melhor seria não bloquear e passar um lambda para limpeza:

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

Uma coisa a ter em mente é que você deve ligar para o EndInvoke. Muitas pessoas esquecem isso e acabam vazando o WaitHandle, pois a maioria das implementações assíncronas libera o handhandle no EndInvoke.


2

Se você for usar o objeto Thread bruto, precisará configurar o IsBackground como true no mínimo e também definir o modelo Threading Apartment (provavelmente 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()
}

Eu recomendaria a classe BackgroundWorker se você precisar de interação da interface do usuário.


Tenha cuidado ao definir o IsBackground como true. Provavelmente não faz o que você pensa que faz. O que ele faz é configurar se o encadeamento será eliminado quando todos os encadeamentos em primeiro plano morrerem ou se o encadeamento manterá o aplicativo ativo. Se você não deseja que seu encadeamento seja finalizado no meio da execução, não defina IsBackground como true.
Zero3 22/09


1

outra opção, que usa delegados e o Pool de threads ...

assumindo 'GetEnergyUsage' é um método que usa um DateTime e outro DateTime como argumentos de entrada e retorna um 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);

11
Charles, seu código é funcionalmente diferente de int usageCnt = nrgDel.Invoke(lastRunTime, procDT, null, null);? Parece que suspende thread atual com o sono de qualquer maneira ... Eu pensei que ele vai ajudar nada com GUI congelamento se você chamá-lo em fio de GUI
IgorK

Sim, BeginInvoke chama o delegado em outro encadeamento do pool de encadeamentos ... Se você usar Invoke, o delegado será chamado de forma sincronizada no encadeamento atual ... Mas você está certo, o sono está errado ... você deve eliminar isso e colete os resultados usando uma função de retorno de chamada. Vou editar para mostrar isso
Charles Bretana 12/12/08

1

Existem várias maneiras de executar threads separados no .Net, cada um com comportamentos diferentes. Você precisa continuar executando o encadeamento após o encerramento da GUI? Você precisa passar informações entre o thread e a GUI? O thread precisa atualizar a GUI? O encadeamento deve executar uma tarefa e encerrar ou deve continuar em execução? As respostas a essas perguntas lhe dirão qual método usar.

Há um bom artigo sobre método assíncrono no site do Code Project que descreve os vários métodos e fornece código de exemplo.

Observe que este artigo foi escrito antes da introdução do padrão async / waitit e da Task Parallel Library no .NET.


Esse é o tipo de informação que eu estava procurando, mas sua resposta é vítima de podridão de link.
26418 Chris

Obrigado por me informar, @ Chris; link fixo.
Dour High Arch

0

Como usar um segmento de plano de fundo para procurar arquivos

Você precisa ter muito cuidado com o acesso de outros threads a coisas específicas da GUI (é comum em muitos kits de ferramentas da GUI). Se você deseja atualizar algo na GUI a partir do processamento do thread, verifique esta resposta que eu acho útil para o WinForms. Para o WPF, veja isso (mostra como tocar o componente no método UpdateProgress () para funcionar com outros threads, mas, na verdade, eu não gosto que isso não esteja ocorrendo CheckAccess()antes do BeginInvokeDispathcer, consulte e procure o CheckAccess nele)

Estava procurando um livro específico do .NET sobre encadeamento e encontrou este (disponível para download gratuito). Consulte http://www.albahari.com/threading/ para obter mais detalhes.

Acredito que você encontrará o que precisa para iniciar a execução como novo encadeamento nas primeiras 20 páginas e tem muito mais (não tenho certeza sobre trechos específicos da GUI, quero dizer estritamente específicos ao encadeamento). Ficaria feliz em ouvir o que a comunidade pensa sobre este trabalho, porque eu estou lendo este. Por enquanto, parecia bem legal para mim (por mostrar métodos e tipos específicos do .NET para threading). Também abrange o .NET 2.0 (e não o antigo 1.1) o que eu realmente aprecio.


0

Eu recomendo consultar a Power Threading Library de Jeff Richter e, especificamente, o IAsyncEnumerator. Dê uma olhada no vídeo de Charlie Calvert blog de onde Richter o para uma boa visão geral.

Não se deixe levar pelo nome, porque isso facilita a codificação das tarefas de programação assíncrona.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.