【问题标题】: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() 方法,如本答案所述:stackoverflow.com/a/31778592/1633949

标签: c# .net multithreading


【解决方案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(),如以下答案所述:stackoverflow.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 事件没有触发。但是,当我在这里尝试接受类似的答案时[stackoverflow.com/questions/23112676/… 它起作用了。
  • @sooprise Ctrl + .在这些情况下为您提供帮助! (假设您使用的是 Visual Studio)
【解决方案3】:

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

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

【讨论】:

  • 也是我的最爱。 junction 的快速线。我在快速拨号(sn-p)上有它。与控件配合得很好。你只需要知道如何使用InvokeInvokeRequired
  • +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 的当前实现。参见示例stackoverflow.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。
      【解决方案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 对其进行了全面了解。

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

          【讨论】:

            猜你喜欢
            • 2022-11-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-02-22
            • 2011-09-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多