这是老式的使用方式,可以作为了解
1、有了Task还需要学习Thread吗?
不必深入学习,但可以了解。
虽然Task和async/await是更现代和推荐的异步编程模型,但了解Thread类仍然是有益的,特别是在处理特定情况或与旧代码交互时。同时,学习Thread类也可以帮助你更好地理解和使用更高级的异步编程模型。
(1)遗留代码:
在现有的代码库中,可能仍然存在使用Thread类的代码。了解Thread类可以帮助你理解和维护这些代码。
(2) 低级控制:
Thread类提供了对线程的更低级控制。它允许你直接创建、启动、暂停、终止和管理线程的执行。这对于某些特定的场景可能是必要的。
(3)深入理解:
学习Thread类可以帮助你更深入地理解多线程编程的概念和原理。这对于理解并发性、同步、竞争条件等概念是有益的。
(4)调试和故障排除:
了解Thread类可以帮助你更好地调试和排查与多线程相关的问题。当遇到线程相关的bug或性能问题时,对Thread类的了解可以帮助你更快地定位和解决问题。
2、Thread类
Thread类是用于创建和管理线程的基本类。它提供了一组方法和属性,用于创建、启动、暂停、终止和管理线程的执行。
(1)构造函数:
Thread类提供了多个构造函数,用于创建新的线程对象。可以通过传递一个委托或Lambda表达式来指定线程要执行的方法。
(2)Start方法:
通过调用Thread对象的Start方法来启动线程。启动线程后,它会开始执行指定的方法。
(3)Join方法:
可以使用Join方法来等待线程的完成。调用Join方法会阻塞当前线程,直到指定的线程执行完成。
(4)Sleep方法:
可以使用Sleep方法来暂停当前线程的执行一段时间。它接受一个时间间隔参数,指定线程要暂停的时间。
(5)Abort方法:
可以使用Abort方法来终止线程的执行。调用Abort方法会引发ThreadAbortException异常,可以通过捕获该异常来进行适当的处理。
(6)Priority属性:
可以使用Priority属性来设置线程的优先级。较高优先级的线程在竞争资源时会更有可能被执行。
(7)IsAlive属性:
可以使用IsAlive属性来检查线程是否仍在运行。如果线程正在执行或已经启动但尚未完成,则IsAlive属性返回true。
(8)ThreadState属性:
可以使用ThreadState属性来获取线程的当前状态。它返回一个ThreadState枚举值,表示线程的状态,如Running(运行中)、Stopped(已停止)、Suspended(已暂停)等。
注意:
直接使用Thread类来创建和管理线程相对较底级,需要手动处理线程的生命周期、同步和错误处理。在现代的C#编程中,更推荐使用更高级的异步编程模型,例如使用Task和async/await关键字来实现异步操作,而不是直接使用Thread类。这样可以更方便地管理和编写异步代码,并且能够更好地利用系统资源。
3、Thread是异步线程,不会阻塞主调线程
Thread类创建的线程是异步线程,它们在后台运行,并不会阻塞主调线程。当调用Thread.Start方法启动一个新的线程时,主调线程会继续执行后续的代码,而新线程会在后台执行指定的方法。(Thread.Sleep是同步阻塞睡眠)
这意味着,主调线程和新线程可以并发执行,它们之间的执行是相互独立的。主调线程不会等待新线程完成,而是继续执行自己的任务。
如果需要等待新线程完成并获取其结果,可以使用Thread.Join方法。Thread.Join方法会阻塞主调线程,直到新线程执行完成。例如:
Thread th = new Thread(SomeMethod);
th.Start();
th.Join();// 等待新线程执行完成
// 在新线程执行完成后继续执行其他代码
上面主调线程调用了Thread.Join方法,它会等待新线程执行完成。只有当新线程执行完成后,主调线程才会继续执行后续的代码。
注意,如果主调线程在等待新线程执行完成的过程中被阻塞,那么它也会等待新线程执行完成。因此,在使用Thread.Join方法时需要注意避免死锁和长时间的阻塞。
问:如何理解join?
答:join是参加,加入之意。意思是,把异步线程加入到当前线程所处的位置点,当前线程会在这个位置点,等待这个新加入的线程办理完成后,再继续向下执行。因此它是同步阻塞当前线程的,容易假死,有点类型Task.Wait()。
1、三种创建方式:
private void button1_Click(object sender, EventArgs e)
{
//一,无参数线程
Thread th1 = new Thread(Test1);
th1.Start();
//二,有参数线程
int nn = 32;
Thread th2 = new Thread(Test2);
th2.Start(nn);
//三,带堆栈大小
string[] s = { "a", "b", "c", "d" };
Thread th3 = new Thread(Test3, 3);
th3.Start(s);
}
private void Test1()
{
Thread.Sleep(1000);
Invoke(new Action(() =>
{
treeView1.Nodes.Add("无参数线程。");
}));
}
private void Test2(object o)
{
int n = (int)o;
Invoke(new Action(() =>
{
treeView1.Nodes.Add($"有参数线程:{n}");
}));
}
private void Test3(object o)
{
string[] s = o as string[];
Invoke(new Action(() =>
{
treeView1.Nodes.Add($"有参数线程:{string.Join("", s)}");
}));
}
方式一:
不需要参数,直接使用方法签名使用,类似委托Action。
方式二:
需要一个参数输入参数,即类似Action
当线程需要的堆栈超过默认大小时,可以通过设置更大的堆栈来满足需求。然而,确定何时需要设置更大的堆栈大小是具体情况而定的,通常需要根据实际需求和对程序的了解来决定。
最大堆栈大小的范围是有限的,具体取决于操作系统和硬件的限制。一般情况下,最大堆栈大小的上限是操作系统和硬件所能支持的最大值。例如,在Windows操作系统上,最大堆栈大小一般是1MB或2MB,但具体取决于操作系统版本和硬件架构。
如果设置的最大堆栈大小超过了操作系统和硬件的限制,可能会引发异常或导致程序崩溃。因此,在设置最大堆栈大小时,需要确保不超过操作系统和硬件的限制。
在实际使用中,如果没有特殊需求,推荐不手动设置最大堆栈大小,让系统根据默认设置进行堆栈分配。只有在确实需要更大的堆栈空间时,才考虑进行设置,并且需要根据具体情况进行测试和调整,以确保不超过操作系统和硬件的限制。
2、上面因为要操作UI线程,使用了Invoke,也可使用BeginInvoke。
当然也可以使用CheckForIllegalCrossThreadCalls,检查非法交叉线程调用。即正常情况UI线程是不允许其它线程,比如上面的创建的Thread线程来读写UI控件属性等,会有检查机制禁止这样操作,此项在form1的构造方法时,设置为false这样就可以在Thread直接跨线程操作UI线程的控件。
public Form1()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = false;
}
3、什么是CheckForIllegalCrossThreadCalls?
CheckForIllegalCrossThreadCalls是一个静态属性,用于设置或获取一个值,该值指示是否在调用控件的方法时检查线程间调用的合法性。
在Windows Forms应用程序中,UI元素(如窗体、控件)通常只能在创建它们的线程上访问和操作。如果在非创建线程上访问或操作UI元素,可能会引发InvalidOperationException。这是因为UI元素通常不是线程安全的,即不能从多个线程同时访问它们。
CheckForIllegalCrossThreadCalls属性的默认值为true,表示在调用UI元素的方法时会进行线程间调用的合法性检查。如果检测到在非创建线程上访问UI元素,会抛出InvalidOperationException。这个检查可以帮助开发人员在开发过程中及时发现并修复线程间调用的问题。
但是,在某些情况下,我们可能需要在非创建线程上访问UI元素,例如在异步操作中更新UI。在这种情况下,可以将CheckForIllegalCrossThreadCalls属性设置为false,禁用合法性检查。这样,就可以在非创建线程上访问UI元素,但需要确保在访问UI元素时进行适当的线程同步。
注意,禁用CheckForIllegalCrossThreadCalls属性可能会导致UI元素的线程安全问题,因此在使用时需要谨慎,并确保在非创建线程上访问UI元素时进行正确的线程同步操作。
4、Thread存在传入参数的问题。而Task不存在此类问题。
对于Task因为闭包原因,可以随便访问方法外部的变量,所以它还存在需要传入参数吗?
```csharp
public static void Main()
{
int num = 10;
Task task = Task.Run(() => {
Console.WriteLine("执行异步操作,参数为:" + num);
});
task.Wait();
}
上面异步线程里,直接在task内访问一个外部变量num的值。通过使用闭包的特性,我们可以在异步操作中访问并使用外部的变量。
注意,当使用Task传递参数时,需要确保参数的原子性和竞争问题。如果多个Task同时访问和修改同一个外部变量,可能会导致数据不一致或竞争条件的问题。为了解决这个问题,可以使用锁机制或其他线程同步方式来保证数据的一致性和线程安全性。
5、什么是实体类?
在C#中,实体类(Entity Class)是用于表示具体实体或对象的类。它通常包含属性、字段和方法,用于描述实体的特征和行为。实体类的实例通常用于表示数据库中的表或其他现实世界的实体。 实体类是通过定义一个类来创建的,该类具有与实体相关的属性和方法。例如,如果我们要表示一个学生实体,我们可以创建一个名为Student的类,该类包含属性如姓名、年龄、学号等,以及方法如学习、休息等。
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public string StudentId { get; set; }
public void Study()
{
// 学生学习的逻辑
}
public void Rest()
{
// 学生休息的逻辑
}
}
上面Student类是一个实体类,用于表示学生实体。它具有三个属性:Name、Age和StudentId,用于表示学生的姓名、年龄和学号。它还具有两个方法:Study和Rest,用于表示学生的学习和休息行为。 相对于实体类,虚类(Virtual Class)是一个可以被继承的类,它允许其他类继承它并重写其方法。虚类用关键字"virtual"来声明,它可以提供一个基类的框架,但允许派生类根据自己的需求进行扩展和修改。
public virtual class Shape
{
public virtual double CalculateArea()
{
return 0;
}
}
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public override double CalculateArea()
{
return Width * Height;
}
}
上面 Shape类是一个虚类,用于表示形状的基类。它具有一个虚方法CalculateArea,用于计算形状的面积。Rectangle类继承自Shape类,并重写了CalculateArea方法,以计算矩形的面积。
通过使用虚类和重写方法,可以创建一个基类,允许派生类根据自己的需求进行自定义。这样可以提供更灵活的类结构和更好的代码扩展性。
Thread在传参数时,可以使用实体类,达到一个参数带入多个参数的目的。
6、什么是计算机执行流?
执行流(execution flow)是指计算机程序在执行过程中的控制流程,即程序的执行顺序。计算机程序一般是由一系列的语句组成,执行流决定了这些语句的执行顺序和条件。
在C#中,执行流可以通过控制结构(如条件语句、循环语句和跳转语句)来控制。根据条件的不同,程序的执行流可以按照不同的路径执行不同的语句。例如,条件语句(如if语句)可以根据条件的真假来选择执行不同的代码块;循环语句(如for循环)可以重复执行一段代码块直到满足退出条件;跳转语句(如break和continue语句)可以改变程序的执行流程。
执行流的控制对于程序的正确性和效率非常重要。程序员需要仔细设计和控制执行流,以确保程序按照预期的方式执行,并且能够正确地处理各种情况和条件。
执行流代表着程序代码的执行顺序。执行流通常与主线程(也称为UI线程或主线程)相关联,该线程负责处理用户界面和与用户的交互。
Thread类可以用于创建新的执行流,这些执行流在不同的线程上运行。这意味着你可以使用Thread类创建新的线程来执行特定的任务,而不会影响主线程的执行流。
1、线程执行流完成后,线程会怎么样?
当由线程池创建的线程完成执行后,会自动返回到线程池中,并且可以被重用来执行其他任务。线程池会管理和控制线程的生命周期,包括创建、分配、执行和回收。
线程池的目的是为了提高应用程序的性能和效率。通过重用线程,避免了频繁创建和销毁线程的开销,减少了系统资源的消耗。线程池可以根据需要动态调整线程的数量,以适应不同的负载情况。
当线程池中的线程完成执行后,它会立即返回到线程池中,等待下一个任务的分配。这样可以避免线程的频繁创建和销毁,提高了线程的利用率和整体性能。
注意,线程池是由CLR(Common Language)来管理和控制的,具体实现细节可能会因不同的运行时环境或操作系统而有所差异。但无论如,线程池都会负责管理线程的分配和回收,以提高应用程序的性能和效率。
2、如果强制用Abort中断线程,会怎么样?
一旦由Thread创建的线程完成其执行,它将自动止并退出。当线程运行完成时,它会自动释放所占用的资源,并且不会再占用任何CPU时间。
在某些情下,可能需要提前终止正在运行的线程,而不是等待它自然结束。这时可以使用Thread.Abort方法来中止线程的执行。Thread.Abort方法会引发一个ThreadAbortException异常,该异常会在线程执行到适当的中断点时被捕获,从而中止线程的执行。
使用Thread.Abort方法需要谨慎,因为中止一个线程可能会导致资源泄漏、数据不一致等问题。线程的终止应该是一种紧急的手段,只在必要的情况下使用。推荐在可能的情况下,通过合理的设计和协调来实现线程的正常退出,而不是强制终止线程。
总结,Thread.Abort方法用于中止正在运行的线程,但应该慎重使用,并且只在必要的情况下才使用。在大多数情况下,应该通过合理的设计和协来确保线程的正常结束。
3、Abort可能影响程序的运行?
是的,调用Thread.Abort方法会引发ThreadAbortException异常这个异常会传播到线程的执行点,并且会导致线程立即中止执行。
ThreadAbortException异常会终止线程的执行并且不被捕获,这可能导致未完成的操作、泄漏或数据不一致等问题。因此,使用Thread.Abort方法需要谨慎,并且应该尽量避免在正常况下使用它。
当ThreadAbortException异常被引发时,线程会尽快终止,并且不会执行任何finally块中的。这可能会导致一些清理操作无法完成,例如释放资源或关闭文件等。
为了避免程序运行被异常中,可以在线程中使用try-catch块来捕获ThreadAbortException异常,并且在捕获到该异常时进行适当的处理,例如进行清理操作或安全地终止线程的执行。
总结,调用Thread.Abort方法引发ThreadAbortException异常,这可能会影响程序的运行。因此,建议在正常情况下避免使用Thread.Abort方法,并且在使用时要注意异常处理,以确保程序可以正常退出或终止线程的执行。
4、Priority属性(优先级)
Thread类中的Priority属性用于设置和获取线程的优先。线程的优先级决定了线程在竞争CPU资源时的优先级顺序。
线程的优先级可以设置为以下几个级别(按高到低的顺序):
Highest:最高优级
AboveNormal:高正常优先级
-:正常优级(默认)
BelowNormal:低于正常优先级
Lowest:最低优先级
线程的优先级并不是绝对的,而是相对的,它只是影响线程在CPU调度时的概率。优先级高的线程在竞争CPU资源时更有可能被调度执行,但并不保证优先级高的线程一定会在优先级低的线程之前执行。
举例来说,如果有一个CPU密集型任务和一个I/O密集型任务同时运行,可以将CPU密集型任务的线程优级设置为Highest,将I/O密集型任务的线程优先级设置为Normal。这样可以让CPU密集型任务在竞争CPU资源时更有可能被调度执行以提高CPU的利用率。
注意,对于多核或多处理器系统,优先级的影响可能会有所不同。在这种情况下,每个处理器核心或处理器可以有自己的调度队列,优先级可能会在这些队列之间有所影响。
总结,Thread的Priority属性用于设置和获取线程的优先级,并且可以影响线程在竞争CPU资源时的调度顺序。优先级高的线程更有可能被调度执行,但并保证一定会在优先级低的线程之执行。
5、线程状态:
IsAlive属性和ThreadState属性是相关的,它们都提供了关于线程状态的信息。
IsAlive属性是一个只读属性,用于判断线程是否仍然处于活动状态。
当线程已经完成执行、终止或尚未启动时,IsAlive属性返回false。
当线程正在执行,IsAlive属性返回true。
当一个线程尚未启动时,IsAlive属性返回false。只有在线程启动后,才会返回true。
ThreadState属性是一个只读属性,用于获取当前线的状态。ThreadState属性返回一个ThreadState枚举值,表示线程的当前状态。ThreadState枚举包括以下一些状态:
Unstarted:线程尚未启动
Running:线程正在执行中
WaitSleepJoin:线程正在等待、睡眠或加入其他线程
Suspended:线程被挂起
Stopped:线程已经停止
Aborted:线程已经中止
Background:线程是一个后台线程
问:什么是挂起Suspended?
答:挂起线程可以类比为在看病过程中,医生暂停了当前正在处理的病人,去处理一个病危的紧急病人。
当一个线程被挂起时,它的执行暂停,类似于医生暂停了当前正在处理的病人。挂起线程的目的是为了处理一些紧急情况或者满足特定条件后再恢复执行,就像医生需要快速处理病危的人一样。
注意,Thread.Suspend和Thread.Resume方法在现代的多线程编程中已经不推荐使用,因为它们可能导致一些问题,如死锁、资源争用等。取而代之的是,应该使用更安全和可靠的线程同步机制,如Monitor、Semaphore、Mutex等。
挂起后的线程isAlive可能是false(表示它熄火了,别人线程完全抢占且不让出来),可能是true(表示活着,别的线程可能随后会让出来让它再次使用进行)
通过检查ThreadState属性可以了解线的当前状态,从而根据需要进行适当的处理或等待。
使用示例:
Thread th = new Thread(() =>
{
Console.WriteLine("线程开始...");
Thread.Sleep(2000);
Console.WriteLine("线程结束...");
});
for (int i = 0; i < 10; i++)
{
Console.WriteLine(th.ThreadState);
if (i == 1) th.Start();
Thread.Sleep(200);
}
结果:
Unstarted
Unstarted
线程开始...
WaitSleepJoin
WaitSleepJoin
WaitSleepJoin
WaitSleepJoin
WaitSleepJoin
WaitSleepJoin
WaitSleepJoin
线程结束...
Stopped
IsAlive属性用于判断线程是否仍处于活动状态,而ThreadState属性用于获取线程的当前状态。通过使用这两个属性,可以监控和控制线程的执行过程。