您的位置:首页 > 产品设计 > UI/UE

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个不同的优先级,由高到低分别是HighestAboveNormalNormalBelowNormalLowest,在创建线程时如果不指定优先级,那么系统默认为
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类执行什么类型的任务,加工什么参数,而无需再去考虑其余事情。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: