C# 多线程详解 Part.01(UI 线程、子线程)
2018-01-04 08:35
369 查看
基本概念
什么是进程?当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。一个进程至少有一个主线程。
什么是线程?
线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。
什么是多线程?
多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
多线程的好处?
可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。
多线程的不利方面?
线程也是程序,所以线程需要占用内存,线程越多占用内存也越多。
多线程需要协调和管理,所以CPU需要花时间来跟踪线程。
线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题。
线程太多会导致控制太复杂,最终可能造成很多Bug。
staticvoidMain(string[]args)
{
Thread.CurrentThread.Name="It'sMainThread";
Console.WriteLine(Thread.CurrentThread.Name+"[Status:"+Thread.CurrentThread.ThreadState+"]");
}
通过Thread类的静态属性CurrentThread可以获取当前正在执行的线程。不管创建了多少个这个类的实例,但是类的静态属性在内存中只有一个。很容易理解CurrentThread为什么是静态的--虽然有多个线程同时存在,但是在某一个时刻,CPU只能执行其中一个!
在.netframeworkclasslibrary中,所有与多线程机制应用相关的类都是放在System.Threading命名空间中。
Thread类有几个至关重要的方法:
Start() | 启动线程 |
Sleep(int) | 静态方法,暂停当前线程指定的毫秒数 |
Abort() | 通常使用该方法来终止一个线程 |
Suspend() | 该方法并不终止未完成的线程,它仅仅挂起线程,以后还可恢复 |
Resume() | 恢复被Suspend()方法挂起的线程的执行 |
操纵一个线程
staticvoidMain(string[]args)
{
Console.WriteLine("ThreadStart/Stop/JoinSample:");
//创建一个线程,使之执行Beta方法
ThreadoThread=newThread(Beta);
//实际上,Start方法只是通知CPU此线程可以被执行,但具体执行时机则由CPU自行决定。
oThread.Start();
while(!oThread.IsAlive)
{
Thread.Sleep(1);
}
oThread.Abort();
oThread.Join();
Console.WriteLine();
Console.WriteLine("Betahasfinished");
try
{
Console.WriteLine("TrytorestarttheAlpha.Betathread");
oThread.Start();
}
catch(ThreadStateException)
{
Console.WriteLine("ThreadStateExceptiontryingtorestartAlpha.Beta.");
Console.WriteLine("Expectedsinceabortedthreadscannotberestarted.");
Console.ReadLine();
}
}
publicstaticvoidBeta()
{
while(true)
{
Console.WriteLine("Betaisrunninginitsownthread.");
}
}
试图用Thread.Start()方法重新启动线程oThread,但显然Abort()方法带来的后果是不可恢复的终止线程,所以最后程序会抛出ThreadStateException异常。
线程的优先级
当线程之间争夺CPU时,CPU按照线程的优先级给予服务。在C#应用程序中,用户可以设定5个不同的优先级,由高到低分别是Highest,AboveNormal,Normal,BelowNormal,Lowest,在创建线程时如果不指定优先级,那么系统默认为ThreadPriority.Normal。
通过设定线程的优先级,我们可以安排一些相对重要的线程优先执行,例如对用户的响应等等。
staticvoidMain(string[]args)
{
Threadt1=newThread(()=>
{
System.Diagnostics.Stopwatchwatch=newSystem.Diagnostics.Stopwatch();
watch.Start();
for(inti=0;i<20000;i++)
{
//模拟耗时工作
varobj=new{name="XXX",age=37};
GC.Collect();
}
watch.Stop();
Console.WriteLine("t1finished[{0}]",watch.ElapsedMilliseconds);
});
Threadt2=newThread(()=>
{
System.Diagnostics.Stopwatchwatch=newSystem.Diagnostics.Stopwatch();
watch.Start();
for(inti=0;i<20000;i++)
{
varobj=new{name="XXX",age=37};
GC.Collect();
}
watch.Stop();
Console.WriteLine("t2finished[{0}]",watch.ElapsedMilliseconds);
});
t1.Priority=ThreadPriority.AboveNormal;
t2.Priority=ThreadPriority.BelowNormal;
t1.Start();
t2.Start();
}
线程的调度算法非常复杂。记住,优先级高的线程并不一定先执行,但CPU会将更多的时间片分给优先级高的线程,因此,在相同任务量的前提下,高优先级线程将会较快的完成任务。
Winform中多线程的应用
在Winform程序总,一般称绘制窗体和响应用户的线程为主线程,或UI线程。单线程最显著的缺点是,当一个事件发生,程序进行一个耗时的运算动作时,UI线程会出现假死现象,此时会无视对用户的响应。下面的代码会模拟一些不同的情况:
voidDoSomething()
{
for(inti=0;i<900000000;i++)
{
}
MessageBox.Show("It'sfinished.");
}
voidShowStr(objectobj)
{
varlist=objasList<string>;
if(list!=null)
{
foreach(variteminlist)
{
MessageBox.Show(item.ToString());
}
}
else
MessageBox.Show("null");
}
//UI单线程,运行时窗体会卡死一段时间
privatevoidbtnUI_Click(objectsender,EventArgse)
{
DoSomething();
}
//调用无參函数,此时窗体能响应用户
privatevoidbtnThreadA_Click(objectsender,EventArgse)
{
Threadthread=newThread(DoSomething);
thread.Start();
}
//当所有前台线程都关闭时,后台线程将立即结束运行,无条件的关闭
//而前台线程运行时,即使关闭Form主程序,该线程仍将继续运行,直到计算完毕
privatevoidbtnThreadB_Click(objectsender,EventArgse)
{
Threadthread=newThread(DoSomething);
thread.IsBackground=true;
thread.Start();
}
//调用有参函数
privatevoidbtnThreadC_Click(objectsender,EventArgse)
{
Threadthread=newThread(ShowStr);
thread.Start(newList<string>{"Jacky","Skysoot","Sam"});
}
要注意的是,线程在调用有参函数时,通过Start()方法传递了参数给指定委托,该委托又将参数传递给了该线程欲运行的函数。看微软Thread类定义的元数据:
Thread类的4个构造函数基本分为2类,有参和无參。而ParameterizedThreadStart委托定义的方法原型的参数为Object类型,这提高了传参最大的灵活性。当然,在被调用的函数内部,需要依据一定的约定将Object对象进行转型处理。
绕圈子封装一个线程类进行函数和参数的传递
//可定义各类型委托示例暂定一个
publicdelegatevoidDo(objectobj);
publicclassWorker
{
Domethod;
objectobj;
privatevoidWork()
{
method(obj);
}
//创建工人线程时new出工人实例并在线程上指定Work()
publicstaticThreadCreateWorkerThread(Domethod,objectobj)
{
Workerworker=newWorker();
worker.method=method;
worker.obj=obj;
Threadt=newThread(worker.Work);
returnt;
}
}
//任务类
publicclassQuest
{
publicstaticvoidQuest1(objectobj)
{
Console.WriteLine("工人开始:"+obj.ToString()+"\r\n");
}
publicstaticvoidQuest2(objectobj)
{
string[]list=objasstring[];
if(obj!=null)
{
foreach(variteminlist)
{
Console.WriteLine("工人开始:"+item);
}
}
}
}
publicclassTest
{
publicstaticvoidMain(string[]args)
{
Threadt1=Worker.CreateWorkerThread(Quest.Quest1,"搬砖");
t1.Start();
Threadt2=Worker.CreateWorkerThread(Quest.Quest2,newstring[]{"唱歌","跳舞","打台球"});
t2.Start();
}
}
这种封装只是一种启发的方式而已,并非模式。但封装委托后的好处在于,调用方可以灵活指定Worker类执行什么类型的任务,加工什么参数,而无需再去考虑其余事情。
相关文章推荐
- C# 多线程详解 Part.01(UI 线程、子线程)
- C# 多线程详解 Part.02(UI 线程和子线程的互动、ProgressBar 的异步调用)
- C# 多线程详解 Part.02(UI 线程和子线程的互动、ProgressBar 的异步调用)
- 【WPF学习】WPF、WinForm(C#)多线程编程并更新界面(UI)/子线程更新主界面方式
- C#中的多线程与UI响应-防界面假死的多线程
- c#多线程(UI线程,控件显示更新) Invoke和BeginInvoke 区别
- 第一个WPF学习例子--多线程、任务和UI线程一起使用、方法重载 c#书籍
- !!c# 委托与多线程 -- 相比background来说,本方法针对当需要通过异步代理发起UI操作,而后线程中进行sleep操作的时使用
- C#多线程学习之如何操纵一个线程
- 【Boost】boost库中thread多线程详解5——谈谈线程中断
- c# 多线程详解
- [C#]极简的多线程操作 & 跨线程调用控件
- C# 线程手册 第五章 多线程应用程序 .NET 中的扩展性
- C#后台线程和UI的交互
- 多线程在UI显示线程运行状态信息
- C#用副线程改主线程(UI线程)的控件属性的方法(包括Winform和WPF)
- C#多线程学习之(二)操纵一个线程的方法
- C# 实现多线程的同步方法详解
- C#利用委托跨线程更新UI数据
- c#使用多线程的几种方式示例详解