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

Posted

技术标签:

【中文标题】如何在新线程中运行一些简单的代码?【英文标题】:How do I run a simple bit of code in a new thread? 【发布时间】:2010-09-26 16:13:51 【问题描述】:

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

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

【问题讨论】:

当时这里的大多数答案都很好,但 .NET Framework 4.0 的改进使事情变得更简单。您可以使用 Task.Run() 方法,如本答案所述:***.com/a/31778592/1633949 【参考方案1】:

开始阅读的好地方是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 - 是的,它可以在 Web 表单中工作。 Start here: 小心将IsBackground 设置为true。它可能不会像您认为的那样做。它的作用是配置当所有前台线程都死了时线程是否会被杀死,或者线程是否会让应用程序保持活动状态。如果您不希望线程在执行过程中终止,请不要将 IsBackground 设置为 true。 @EdPower 我认为无论哪种方式你都应该小心!您可能不想在执行过程中终止的任务示例之一是将数据保存到磁盘。但是可以肯定的是,如果您的任务适合随时终止,那么标记就可以了。我的意思是人们在使用标志时需要小心,因为你没有描述它的用途,而且它的命名很容易让人相信它所做的事情与实际所做的事情不同。 使用 .NET Framework 4.0+ 只需使用 Task.Run(),如以下答案所述:***.com/a/31778592/1633949【参考方案2】:

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 是安全的 ProgressChangedRunWorkerCompleted 处理程序。 但是,从DoWork 更新 GUI 将造成 InvalidOperationException

【讨论】:

使用 System.ComponentModel; (可能会避免人们进行谷歌搜索,感谢这个有用的代码示例)+1 @Gant 感谢您提供示例代码。当我尝试您的代码时,ProgressChanged 事件没有触发。但是,当我在这里尝试接受类似的答案时[***.com/questions/23112676/… 它起作用了。 @sooprise Ctrl + .在这些情况下为您提供帮助! (假设您使用的是 Visual Studio)【参考方案3】:

ThreadPool.QueueUserWorkItem 非常适合简单的事情。唯一需要注意的是从另一个线程访问控件。

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

【讨论】:

也是我的最爱。 junction 的快速线。我在快速拨号(sn-p)上有它。与控件配合得很好。你只需要知道如何使用Invoke 和InvokeRequired。 +1 请注意,委托也允许您整齐地包装参数。 如何使用带有回调函数的 ThreadPool.QueueUserWorkItem 意味着当工作完成后回调会通知我们。【参考方案4】:

又快又脏,但它会起作用:

在顶部使用:

using System.Threading;

简单代码:

static void Main( string[] args )

    Thread t = new Thread( NewThread );
    t.Start();


static void NewThread()

    //code goes here

我只是将它放入一个新的控制台应用程序中作为示例

【讨论】:

记住标记为 IsBackground 的线程不会被运行时自动终止。这需要应用程序进行线程管理。如果您将线程标记为非后台线程,则在线程执行后线程将为您终止。 @CmdrTallen:这不太对。标记 IsBackground=true 的线程意味着线程不会阻止进程退出,即当所有 IsBackground=false 的线程都退出时,进程将退出【参考方案5】:

这是另一种选择:

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

【讨论】:

如果你以这种方式创建一个线程,你将如何从 UI 线程中停止这个线程? @slayernoah 您可以将 CancellationTokenSource 作为参数传递给它,并在线程外设置取消令牌:msdn.microsoft.com/en-us/library/dd997364(v=vs.110).aspx 这段代码不能保证你有一个新线程。该决定取决于您正在使用的 ThreadPool 的当前实现。参见示例***.com/questions/13570579/… @GiulioCaccin ThreadPool 可以从其池中选择一个现有线程,但它肯定是不同的线程。【参考方案6】:

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

【讨论】:

您当然可以使用 Thread 类来执行此操作,但 BackgroundWorker 为您提供了线程完成和进度报告的方法,否则您必须自己弄清楚如何使用。不要忘记您必须使用 Invoke 与 UI 对话! 请注意:BackgroundWorker 有一些微妙的限制,但对于常见的“我想离开并做某事并让我的表单保持响应”这太棒了。 @Merus,您能否详细说明这些微妙的限制是什么?【参考方案7】:

如果你想得到一个值:

var someValue;

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

thread.Start();

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

【讨论】:

【参考方案8】:

将该代码放入一个函数中(不能在与 GUI 相同的线程上执行的代码),并触发该代码的执行,放入以下代码。

Thread myThread= new Thread(nameOfFunction);

workerThread.Start();

在线程对象上调用 start 函数会导致你的函数调用在一个新线程中执行。

【讨论】:

@user50612 你在说什么?新线程将在后台运行nameOfFunction,而不是在当前 GUI 线程上。 IsBackground 属性决定线程是否使应用程序保持活动状态:msdn.microsoft.com/en-us/library/…【参考方案9】:

这里如何使用带有progressBar的线程,它只是为了了解线程是如何工作的,形式上有3个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();
    

【讨论】:

【参考方案10】:

如果您要使用原始 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。 文档:msdn.microsoft.com/en-us/library/…【参考方案11】:
// 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 您的代码(上图)不正确。您无需旋转等待完成。 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 中释放了等待句柄。

【讨论】:

【参考方案12】:

另一个选项,使用委托和线程池......

假设 '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);

【讨论】:

Charles,您的代码在功能上是否与 int usageCnt = nrgDel.Invoke(lastRunTime, procDT, null, null); 不同?看起来它无论如何都会用睡眠暂停当前线程......我认为如果你在 GUI 线程中调用它,它对 GUI 冻结没有任何帮助 是的,BeginInvoke 在线程池的另一个线程上调用委托...如果您使用 Invoke,则在当前线程上同步调用委托...但是您是对的,睡眠是错误的...您应该消除它并使用回调函数收集结果。我将进行编辑以显示【参考方案13】:

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

代码项目网站上有一个很好的async method article,它描述了各种方法并提供了示例代码。

请注意,本文是在 async/await 模式和 Task Parallel Library 被引入 .NET 之前编写的。

【讨论】:

这是我正在寻找的信息类型,但您的回答是链接失效的受害者。 感谢您告诉我,@Chris;链接已修复。【参考方案14】:

How to: Use a Background Thread to Search for Files

您必须非常小心地从其他线程访问特定于 GUI 的东西(这在许多 GUI 工具包中很常见)。如果您想从处理线程检查this answer 更新GUI 中的某些内容,我认为这对WinForms 很有用。对于 WPF,请参阅 this(它显示了如何在 UpdateProgress() 方法中触摸组件,因此它可以从其他线程工作,但实际上我不喜欢在通过 Dispathcer 执行 BeginInvoke 之前不执行 CheckAccess(),@987654324 @ 并在其中搜索 CheckAccess)

正在寻找有关线程的 .NET 特定书籍,发现 this one(可免费下载)。有关详细信息,请参阅http://www.albahari.com/threading/。

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

【讨论】:

【参考方案15】:

我建议查看 Jeff Richter 的 Power Threading Library,特别是 IAsyncEnumerator。观看 Charlie Calvert's 博客上的视频,Richter 对其进行了全面了解。

不要被这个名字吓到,因为它使异步编程任务更容易编码。

【讨论】:

以上是关于如何在新线程中运行一些简单的代码?的主要内容,如果未能解决你的问题,请参考以下文章

如果没有这样做,函数如何在新线程上“好像”运行?

在新线程中调用方法的简单方法

如何在新线程上运行任务并立即返回给调用者?

Java ClassLoader 在新线程(可运行)中使用时停止工作

在新窗口 C++ 中创建线程

如何关闭嵌套在线程内的线程?