Як запустити простий біт коду в новій темі?


340

У мене є трохи коду, який мені потрібно запустити в іншому потоці, ніж GUI, оскільки він в даний час призводить до застигання форми під час роботи коду (10 секунд або близько того).

Припустимо, я ніколи раніше не створював нової теми; який простий / основний приклад того, як це зробити в C # та за допомогою .NET Framework 2.0 або новіших версій?


2
Більшість відповідей тут були на той час хорошими, але вдосконалення в .NET Framework 4.0 спрощують справи. Ви можете скористатися методом Task.Run (), як описано у цій відповіді: stackoverflow.com/a/31778592/1633949
Річард II,

Відповіді:


336

Хороше місце для початку читання - Джо Альбахарі .

Якщо ви хочете створити власну нитку, це так просто, як це виходить:

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

@EdPower, це стосується лише Winforms .. чи це працюватиме у веб-формах ..?
МетодМан

@MethodMan - Так, він працюватиме у веб-формах. Почніть тут:
Ed Power

9
Будьте уважні до встановлення IsBackgroundзначення true. Це, мабуть, не робить те, що, на вашу думку, робить. Це робиться, це налаштувати, чи потік буде знищений, коли всі потоки переднього плану загинули, чи потік буде підтримувати додаток живим. Якщо ви не хочете, щоб ваш потік закінчувався в середині виконання, не встановлюйте IsBackgroundзначення true.
Zero3

10
@EdPower Я думаю, вам слід бути обережними в будь-якому випадку! Одним із прикладів завдання, яке, ймовірно, не хочеться закінчувати в середині виконання, є збереження даних на диску. Але впевнено, якщо ваше завдання підходить для припинення в будь-який час, прапор добре. Моя думка полягала лише в тому, що потрібно бути обережним у використанні прапора , оскільки ви не описали його мету, а його іменування легко може привести до того, що він вважає, що він робить щось інше, ніж те, що він насправді робить.
Zero3

3
з .NET Framework 4.0+ просто використовуйте Task.Run (), як описано у цій відповіді: stackoverflow.com/a/31778592/1633949
Річард II

193

BackgroundWorker здається, найкращий вибір для вас.

Ось мій мінімальний приклад. Після натискання на кнопку фоновий працівник почне працювати у фоновому потоці, а також одночасно повідомить про його хід. Він також звітуватиме після завершення роботи.

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();
    }

Примітка:

  • Я вкладаю все в один метод, використовуючи анонімний метод C # для простоти, але ви завжди можете витягнути їх на різні методи.
  • Оновити GUI всередині ProgressChangedабо RunWorkerCompletedобробників можна безпечно . Однак оновлення графічного інтерфейсу від DoWork спричинить InvalidOperationException.

27
Використання System.ComponentModel; (може врятувати людей від пошуку в Google, спасибі за цей корисний приклад коду) +1
sooprise

@Gant Дякую за зразок коду. Коли я спробував ваш код, подія ProgressChanged не спрацювала. Однак коли я спробував подібну прийняту відповідь тут [ stackoverflow.com/questions/23112676/…, це спрацювало.
Мартін

1
@sooprise Ctrl +. допомагає вам у цих ситуаціях! (Припустимо, що ви використовуєте Visual Studio)
SepehrM

100

ThreadPool.QueueUserWorkItem досить ідеально підходить для чого - то просто. Єдине застереження - це доступ до елемента керування з іншого потоку.

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

Також мій улюблений. Одна швидка лінія для стику . Я маю це на швидкому наборі (фрагмент). Дуже добре працює з елементами управління. Вам просто потрібно знати, як використовувати Invoke та InvokeRequired .
Bitterblue

1
+1 Зауважте, що делегат дозволяє також акуратно обертати параметри.
Вілл Бікфорд

як використовувати ThreadPool.QueueUserWorkItem з функцією зворотного дзвінка означає, що коли робота виконана, то передзвоніть нам сповістить про це.
Mou

76

Швидкий і брудний, але він буде працювати:

Використання вгорі:

using System.Threading;

простий код:

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

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

Я просто перекинув це на нову консольну програму для прикладу


8
Пам'ятайте, що потоки, позначені IsBackground, не виконуються автоматично під час виконання. Для цього потрібно керувати потоками через додаток. Якщо ви залишите потік позначеним як не фон, то після виконання ниток ця нитка припиняється для вас.
CmdrTallen

14
@CmdrTallen: Це не зовсім правильно. Потік, позначений IsBackground = true, означає, що потік не зупинить процес виходу, тобто процес вийде, коли всі потоки з IsBackground = false вийдуть
Phil Devaney

66

Ось ще один варіант:

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

1
якщо ви створили нитку таким чином, як би зупинити цю нитку з потоку інтерфейсу?
slayernoah

1
@slayernoah ви можете передати параметр CancellationTokenSource і встановити маркер скасування поза потоком: msdn.microsoft.com/en-us/library/dd997364(v=vs.110).aspx
Spongebob Comrade

7
Цей код не гарантуватиме вам нову нитку. Рішення залежить від поточної реалізації ThreadPool, який ви використовуєте. Див як приклад stackoverflow.com/questions/13570579 / ...
Джуліо Caccin

3
@GiulioCaccin ThreadPool може вибрати існуючий потік зі свого пулу, але це, безумовно, інший потік.
Губчастий товариш

40

Спробуйте використовувати клас BackgroundWorker . Ви даєте йому делегатів, на що працювати, і отримувати повідомлення про те, коли робота закінчена. На прикладі сторінки MSDN є приклад, на який я посилався.


6
Ви, звичайно, можете це зробити з класом Thread, але BackgroundWorker дає вам методи для завершення потоку та звітування про хід, які в іншому випадку вам доведеться з'ясувати, як використовувати себе. Не забувайте, що вам потрібно використовувати Invoke для розмови з інтерфейсом користувача!
Роберт Россні

1
Будьте попереджені: BackgroundWorker має деякі тонкі обмеження, але для загального "Я хочу піти і зробити щось, щоб моя форма залишалася чуйною", це фантастично.
Merus

10
@Merus, ти міг би розширити, які є ці тонкі обмеження?
Крістіан Гудон

14

Якщо ви хочете отримати значення:

var someValue;

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

thread.Start();

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

6

Покладіть цей код у функцію (код, який не може бути виконаний у тому ж потоці, що і GUI), а для запуску виконання цього коду поставте наступне.

Thread myThread= new Thread(nameOfFunction);

workerThread.Start();

Виклик функції запуску на об'єкт потоку спричинить виконання вашого виклику функції в новому потоці.


3
@ user50612 Про що ти говориш? Новий потік буде працювати nameOfFunctionу фоновому режимі, а не на поточному потоці GUI. IsBackgroundВластивість визначає , чи буде потік тримати додаток в живих чи ні: msdn.microsoft.com/en-us/library / ...
ZERO3

5

Ось як можна використовувати потоки з progressBar, його лише для підкреслення того, як працюють потоки, у формі є три кнопки progressBar та 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);

Чарльз ваш код (вище) невірний. Чекати завершення не потрібно. EndInvoke заблокує, поки не надійде сигнал WaitHandle.

Якщо ви хочете заблокувати до завершення, вам просто потрібно

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

або альтернативно

ar.AsyncWaitHandle.WaitOne();

Але який сенс видавати будь-які дзвінки, якщо ви блокуєте? Ви можете просто використовувати синхронний дзвінок. Краще зробити ставку не заблокувати та пропустити в лямбда на прибирання:

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

Варто пам’ятати, що ви повинні зателефонувати в EndInvoke. Багато людей забувають про це і в кінцевому підсумку витікають WaitHandle, оскільки більшість асинхронних програм випускає чеканку в EndInvoke.


2

Якщо ви збираєтесь використовувати об'єкт "Thread raw", тоді потрібно встановити IsBackground на "true" як мінімум, а також слід встановити модель Threading Apartment (можливо, 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()
}

Я рекомендую клас BackgroundWorker, якщо вам потрібна взаємодія з інтерфейсом користувача.


Будьте обережні щодо встановлення IsBackground на істину. Це, мабуть, не робить те, що, на вашу думку, робить. Це робиться, це налаштувати, чи потік буде знищений, коли всі потоки переднього плану загинули, чи потік буде підтримувати додаток живим. Якщо ви не хочете, щоб ваш потік закінчувався в середині виконання, не встановлюйте IsBackground істинним.
Zero3


1

інший варіант, який використовує делегатів та нитку басейну ...

припускаючи, що "GetEnergyUsage" - це метод, який приймає DateTime та інший DateTime як вхідні аргументи та повертає 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);

1
Чарльз, ваш код функціонально відрізняється від int usageCnt = nrgDel.Invoke(lastRunTime, procDT, null, null);? Схоже, він все одно призупиняє поточний потік зі сном ... Я думав, що нічого не допоможе з замороженням GUI, якщо ви зателефонуєте йому в потоці GUI
IgorK

Так, BeginInvoke викликає делегата на інший потік з пулу потоків ... Якщо ви використовуєте Invoke, делегат викликається синхронно на поточному потоці ... Але ви праві, сон невірний ... ви повинні усунути це і збирати результати за допомогою функції зворотного дзвінка. Я редагую, щоб показати, що
Чарльз Бретана

1

Існує багато способів запуску окремих потоків у .Net, кожен має різну поведінку. Чи потрібно продовжувати запускати потік після завершення GUI? Чи потрібно передавати інформацію між потоком та графічним інтерфейсом? Чи потрібно потоці оновлювати графічний інтерфейс? Чи повинен нитка виконати одне завдання, а потім вийти з роботи, чи продовжувати працювати? Відповіді на ці запитання підкажуть, який метод використовувати.

На веб-сайті Code Project є хороша стаття про метод асинхронізації, яка описує різні методи та надає зразок коду.

Зверніть увагу , що ця стаття була написана до асинхронного / ОЧІКУВАННЯ малюнка і Task Parallel Library був введений в .NET.


Це той тип інформації, який я шукав, але ваша відповідь - жертва гниття посилань.
Кріс

Дякую, що повідомили мені, @Chris; посилання виправлено.
Dour High Arch

0

Як: Використовуйте фонову нитку для пошуку файлів

Ви повинні бути дуже обережними з доступом з інших потоків до специфічних GUI (це звичайно для багатьох наборів інструментів GUI). Якщо ви хочете щось оновити в графічному інтерфейсі з обробки потоку, перевірте цю відповідь, що я вважаю корисним для WinForms. Для WPF дивіться це (він показує, як доторкнутися до компонента в методі UpdateProgress (), щоб він працював з інших потоків, але насправді мені не подобається, що це не робиться CheckAccess()перед тим, як робити BeginInvokeчерез Dispathcer, див. і шукайте в ньому CheckAccess)

Шукав .NET специфічну книгу з нарізання різьби і знайшов цю (безкоштовно завантажувану). Детальніше про це див. На веб-сайті http://www.albahari.com/threading/ .

Я вірю, що ви знайдете те, що вам потрібно, щоб запустити виконання як новий потік на перших 20 сторінках, і в ньому є ще багато (не впевнений, у конкретних фрагментах графічного інтерфейсу, я маю на увазі строго специфічну для нитки). Буду радий почути, що громада думає про цей твір, бо я читаю цей. Поки що для мене це виглядало досить акуратно (для показу .NET конкретних методів та типів для нарізки різьби). Також він охоплює .NET 2.0 (а не стародавній 1.1), що я дуже ціную.


0

Я рекомендую переглянути бібліотеку Джефф Ріхтер Power Power Threading та конкретно IAsyncEnumerator. Погляньте на відео в блозі Чарлі Калверта, де Ріхтер переглядає його для гарного огляду.

Не відкладайте ім’я, оскільки це полегшує кодування завдань асинхронного програмування.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.