如何在新线程中运行一些简单的代码?


340

我有一些代码需要在不同于GUI的线程中运行,因为它当前导致表单在代码运行(约10秒左右)时冻结。

假设我以前从未创建过新线程;在C#中使用.NET Framework 2.0或更高版本的简单/基本示例是什么?


2
当时大多数答案都是不错的,但是.NET Framework 4.0的改进使事情变得更简单。您可以使用Task.Run()方法,如以下答案中所述:stackoverflow.com/a/31778592/1633949
理查德二世,

Answers:


336

Joe Albahari是开始阅读的好地方。

如果要创建自己的线程,这很简单:

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

@EdPower,这仅适用于Winforms ..还是可以在Web Forms ..中使用?
MethodMan

@MethodMan-是的,它将在Web窗体中工作。从这里开始:
Ed Power

9
设置IsBackground为true时要小心。它可能没有按照您的想法做。它的作用是配置在所有前台线程都死亡后是否杀死该线程,或者该线程是否使应用程序保持活动状态。如果您不希望线程在执行过程中终止,则不要设置IsBackground为true。
Zero3's

10
@EdPower我认为您都应该小心!您可能不希望在执行过程中终止的一项任务示例是将数据保存到磁盘的示例。但是可以肯定的是,如果您的任务适合随时终止,则该标志很好。我的意思是,您在使用该标志时需要小心,因为您没有描述它的用途,并且它的命名很容易使人相信它所做的事与实际不同。
Zero3 '16

3
使用.NET Framework 4.0+,只需使用Task.Run(),如此答案中所述:stackoverflow.com/a/31778592/1633949
理查德二世

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#的匿名方法将所有内容放在单个方法中,但是您始终可以将它们拉到其他方法中。
  • ProgressChangedRunWorkerCompleted处理程序中更新GUI是安全的 。但是,从更新GUI DoWork 将导致 InvalidOperationException

27
使用System.ComponentModel; (可能会帮助人们避免执行Google搜索,感谢您提供了此有用的代码示例)+1
sooprise 2011年

@Gant感谢您提供示例代码。当我尝试您的代码时,ProgressChanged事件没有触发。但是,当我在这里尝试类似的公认答案时[ stackoverflow.com/questions/23112676/…,它起作用了。
2014年

1
@sooprise Ctrl +。在这些情况下可以为您提供帮助!(假设您使用的是Visual Studio)
SepehrM 2014年

100

ThreadPool.QueueUserWorkItem是相当理想的简单的东西。唯一的警告是从另一个线程访问控件。

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

也是我的最爱。一条快速线用于交叉路口。我将其放在快速拨号(摘要)上。与控件一起很好地工作。您只需要知道如何使用InvokeInvokeRequired即可
Bitterblue 2014年

1
+1请注意,委托还允许您整齐地包装参数。
Will Bickford 2014年

如何将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的线程退出时,进程将退出
Phil Devaney

66

这是另一个选择:

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

1
如果您以这种方式创建线程,您如何从UI线程中停止该线程?
slayernoah's

1
@slayernoah,您可以将CancellationTokenSource作为参数传递给它,并在线程外设置取消令牌: msdn.microsoft.com/zh-cn/library/dd997364
Spongebob同志,

7
此代码不能保证您拥有新线程。该决定取决于您正在使用的ThreadPool的当前实现。见为例stackoverflow.com/questions/13570579/...
Giulio的卡钦

3
@GiulioCaccin ThreadPool可以从其池中选择一个现有线程,但这绝对是一个不同的线程。
海绵宝宝同志

40

尝试使用BackgroundWorker类。您给它委托什么运行,并在工作完成时得到通知。我链接到的MSDN页面上有一个示例。


6
您当然可以使用Thread类来执行此操作,但是BackgroundWorker为您提供了用于完成线程和进行进度报告的方法,否则您将不得不弄清楚如何使用自己。不要忘记,您必须使用Invoke与UI对话!
罗伯特·罗斯尼

1
请注意:BackgroundWorker有一些细微的限制,但是对于常见的“我想离开去做某事并使我的表单保持响应状态”,这真是太棒了。
Merus

10
@Merus,您能否扩展一下这些细微的限制?
Christian Hudon 2012年

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在后台运行,而不是在当前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();

但是,如果阻塞,发出anyc呼叫有什么意义呢?您也可以只使用同步调用。更好的选择是不要阻塞并传递lambda进行清理:

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

要记住的一件事是,您必须调用EndInvoke。许多人忘记了这一点,最终泄漏了WaitHandle,因为大多数异步实现在EndInvoke中释放了waithandle。


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

如果需要UI交互,我建议使用BackgroundWorker类。


将IsBackground设置为true时要小心。它可能没有按照您的想法做。它的作用是配置在所有前台线程都死亡后是否杀死该线程,或者该线程是否使应用程序保持活动状态。如果您不希望线程在执行过程中终止,则不要将IsBackground设置为true。
Zero3 '16


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
Charles,您的代码在功能上是否与int usageCnt = nrgDel.Invoke(lastRunTime, procDT, null, null);?看来它无论如何都将当前线程挂起并处于睡眠状态……我想如果您在GUI线程中调用它,它将对GUI冻结没有任何帮助
IgorK

是的,BeginInvoke在线程池中的另一个线程上调用委托...如果使用Invoke,则在当前线程上同步调用委托...但是您是对的,睡眠是错误的...您应该消除这种情况,并使用回调函数收集结果。我将进行编辑以显示
查尔斯·布雷塔纳

1

在.Net中有很多运行单独线程的方法,每种方法都有不同的行为。GUI退出后是否需要继续运行线程?您是否需要在线程和GUI之间传递信息?线程是否需要更新GUI?线程应该先执行一项任务然后退出,还是应该继续运行?这些问题的答案将告诉您使用哪种方法。

Code Project网站上有一篇很好的异步方法文章,描述了各种方法并提供了示例代码。

请注意,本文是在将异步/等待模式和任务并行库引入.NET 之前编写的。


这是我一直在寻找的信息类型,但是您的答案是link rot的受害者。
克里斯(Chris

感谢您让我知道,@ Chris; 链接固定。
Dour High Arch'Mar

0

如何:使用后台线程搜索文件

从其他线程访问GUI特定内容(在许多GUI工具包中很常见),您必须非常谨慎。如果要从处理线程更新GUI中的某些内容,请检查此答案,我认为这对WinForms很有用。对于WPF看到这个(它显示了如何在的UpdateProgress触摸组件()方法,因此会从其他线程的工作,但实际上我不喜欢它是不是做CheckAccess()在做之前BeginInvoke通过Dispathcer,在它搜索的checkAccess)

正在寻找有关.NET的特定于线程的书,并发现了这一本书(可免费下载)。有关它的更多详细信息,请参见http://www.albahari.com/threading/

我相信您会在前20页中找到作为新线程启动执行所需的内容,并且还有更多内容(不确定特定于GUI的代码片段,我的意思是严格针对线程)。很高兴听到社区对这项工作有何看法,因为我正在阅读此书。现在对我来说看起来很整洁(用于显示.NET特定于线程的方法和类型)。它还涵盖了.NET 2.0(而不是古老的1.1),我对此非常感激。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.