如何在新线程中运行一些简单的代码?
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 是安全的ProgressChanged
或
RunWorkerCompleted
处理程序。
但是,从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 对其进行了全面了解。
不要被这个名字吓到,因为它使异步编程任务更容易编码。
【讨论】:
以上是关于如何在新线程中运行一些简单的代码?的主要内容,如果未能解决你的问题,请参考以下文章