Как запустить простой код в новом потоке?


340

У меня есть немного кода, который мне нужно запустить в потоке, отличном от графического интерфейса, так как в настоящее время он вызывает зависание формы во время выполнения кода (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

@MethodMan - Да, это будет работать в веб-формах. Начните здесь:
Ed Power

9
Будьте осторожны, устанавливая IsBackgroundзначение true. Это, вероятно, не делает то, что вы думаете, что делает. Что он делает, это настраивает, будет ли поток уничтожен, когда все потоки переднего плана умерли, или будет ли поток поддерживать приложение живым. Если вы не хотите, чтобы ваш поток завершался в середине выполнения, не устанавливайте IsBackgroundзначение true.
Ноль3

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

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обработчики. Тем не менее, обновление GUI от 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 с функцией обратного вызова означает, что когда работа выполнена, обратный вызов уведомит нас.
Мо

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 вышли
Фил Девани

66

Вот еще один вариант:

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

1
если вы создадите поток таким образом, как вы остановите этот поток из потока пользовательского интерфейса?
Slayernoah

1
@slayernoah, вы можете передать CancellationTokenSource в качестве параметра и установить токен отмены вне потока: msdn.microsoft.com/en-us/library/dd997364(v=vs.110).aspx
Спанч Боб Товарищ

7
Этот код не гарантирует наличие новой темы. Решение зависит от текущей реализации ThreadPool, который вы используете. См. В качестве примера stackoverflow.com/questions/13570579/…
Джулио Качин

3
@GiulioCaccin ThreadPool может выбрать существующий поток из своего пула, но это определенно другой поток.
Губка Боб, товарищ

40

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


6
Конечно, вы можете сделать это с помощью класса Thread, но BackgroundWorker предоставляет вам методы для завершения потока и создания отчетов о прогрессе, которые в противном случае вам пришлось бы выяснять, как использовать себя. Не забывайте, что вы должны использовать Invoke для общения с пользовательским интерфейсом!
Роберт Росни

1
Будьте осторожны: у BackgroundWorker есть некоторые тонкие ограничения, но для обычного «я хочу уйти и сделать что-то, чтобы моя форма оставалась отзывчивой», это фантастика.
Мерус

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

Вызов функции start для объекта потока приведет к выполнению вызова вашей функции в новом потоке.


3
@ user50612 О чем ты говоришь? Новый поток будет работать nameOfFunctionв фоновом режиме, а не в текущем потоке графического интерфейса. 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();

Но какой смысл делать вызовы anyc, если вы блокируете? Вы могли бы также просто использовать синхронный вызов. Лучше было бы не блокировать и передавать лямбду для очистки:

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

Следует иметь в виду, что вы должны вызывать EndInvoke. Многие люди забывают об этом и в итоге пропускают WaitHandle, так как большинство асинхронных реализаций выпускают дескриптор ожидания в EndInvoke.


2

Если вы собираетесь использовать необработанный объект Thread, вам нужно установить как минимум значение 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 в true. Это, вероятно, не делает то, что вы думаете, что делает. Что он делает, это настраивает, будет ли поток уничтожен, когда все потоки переднего плана умерли, или будет ли поток поддерживать приложение живым. Если вы не хотите, чтобы ваш поток завершался в середине выполнения, не устанавливайте IsBackground в true.
Ноль3


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, если вы
вызовете

Да, BeginInvoke вызывает делегата в другом потоке из пула потоков ... Если вы используете Invoke, делегат вызывается синхронно в текущем потоке ... Но вы правы, сон не так ... вы должны устранить это и собрать результаты с помощью функции обратного вызова. Я отредактирую, чтобы показать это
Чарльз Бретана

1

В .Net существует множество способов запуска отдельных потоков, каждый из которых имеет свое поведение. Вам нужно продолжить запуск потока после выхода из GUI? Нужно ли передавать информацию между потоком и графическим интерфейсом? Поток должен обновить GUI? Должен ли поток выполнить одну задачу, а затем завершиться или продолжить работу? Ответы на эти вопросы скажут вам, какой метод использовать.

На веб-сайте Code Project есть хорошая статья об асинхронных методах, в которой описаны различные методы и приведен пример кода.

Обратите внимание, что эта статья была написана до того, как в .NET были введены шаблон async / await и Task Parallel Library .


Это информация, которую я искал, но ваш ответ является жертвой гниения ссылок.
Крис

Спасибо, что сообщили мне, @Chris; ссылка исправлена.
Dour High Arch

0

Как: использовать фоновую тему для поиска файлов

Вы должны быть очень осторожны с доступом из других потоков к конкретным графическим интерфейсам (это характерно для многих GUI-инструментариев). Если вы хотите обновить что-то в GUI из потока обработки, проверьте ответ, который, на мой взгляд, полезен для WinForms. Для WPF смотрите это (он показывает, как прикоснуться к компоненту в методе UpdateProgress (), чтобы он работал из других потоков, но на самом деле мне не нравится, что он не делает, CheckAccess()прежде чем делать BeginInvokeчерез Dispathcer, смотрите и ищите CheckAccess в нем)

Искал конкретную книгу по .NET и нашел эту (бесплатно скачиваемую). См. Http://www.albahari.com/threading/ для получения более подробной информации об этом.

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


0

Я бы порекомендовал взглянуть на Power Threading Library Джеффа Рихтера и, в частности, на IAsyncEnumerator. Взгляните на видео в блоге Чарли Калверта, где Рихтер просматривает его для хорошего обзора.

Не откладывайте на это имя, потому что это облегчает программирование задач асинхронного программирования.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.