C#中异步多线程的常见⽤法
先来看⼏个基本概念(纯属个⼈见解,可能不准确):
进程:程序运⾏时,占⽤的全部运⾏资源的总和。
线程:线程是⾪属于操作系统管理的,也可以有⾃⼰的计算资源,是程序执⾏流的最⼩单位。任何的操作都是由线程来完成的。
每个线程都在操作系统的进程内执⾏,⽽操作系统进程提供了程序运⾏的独⽴环境。
多线程:多核cpu协同⼯作,多个执⾏流同时运⾏,是⽤资源换时间。(单核cpu,不存在所谓的多线程)。
单线程应⽤:在进程的独⽴环境中只跑⼀个线程,所以该线程拥有独⽴权。
多线程应⽤:单个进程中会跑多个线程,它们会共享当前的执⾏环境(尤其是内存)。
在单核计算机上,操作系统必须为每个线程分配“时间⽚”来模拟并发。⽽在多核或多处理器计算机上,多个线程可以真正的并⾏执⾏。(可能会受到计算机上其他活动进程的竞争)。
win10上的时间⽚(使⽤微软官⽅⼩⼯具测得):
Thread
Thread的对象是⾮线程池中的线程,有⾃⼰的⽣命周期(有创建和销毁的过程),所以不可以被重复利⽤(⼀个操作中,不会出现⼆个相同Id的线程)。
Thread的常见属性:
线程⼀旦开始执⾏,IsAlive就是true,线程结束就变成false。
线程结束的条件是:线程构造函数传⼊的委托结束了执⾏。
线程⼀旦结束,就⽆法再重启。
每个线程都有⼀个Name属性,通常⽤于调试,线程的Name属性只能设置⼀次,以后更改会抛出异常。
静态的Thread.CurrentThread属性,会返回当前线程。
Thread的常见⽤法:
join
调⽤join⽅法可以等待另⼀个线程结束。
private void button5_Click(object sender, EventArgs e){
Console.WriteLine($"===============Method start time is {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},Thread ID is {Thread.CurrentThre ad.ManagedThreadId},is back ground: {Thread.CurrentThread.IsBackground}===================");
//开启⼀个线程,构造⽅法可重载两种委托,⼀个是⽆参⽆返回值,⼀个是带参⽆返回值
Thread thread = new Thread(a => DoSomeThing("Thread"));
//当前线程状态
Console.WriteLine($"thread's state is {thread.ThreadState},thread's priority is {thread.Priority} ,threa
d is alived :{thread.IsAlive},thread is background:{ thread.IsBackground},thread is pool threads: {thread.IsThreadPoolThread}");
//告知操作系统,当前线程可以被执⾏了。
thread.Start();
//阻塞当前执⾏线程,等待此thread线程实例执⾏完成。⽆返回值
thread.Join();
//最⼤等待的时间是5秒(不管是否执⾏完成,不再等待),返回⼀个bool值,如果是true,表⽰执⾏完成并终⽌。如果是false,表⽰已到指定事件但未执⾏完成。
thread.Join(5000);
Console.WriteLine($"===============Method end time is {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},,Thread ID is {Thread.CurrentThre ad.ManagedThreadId},is back ground: {Thread.CurrentThread.IsBackground}===================");
}
private void DoSomeThing(string name)
{
Console.WriteLine($"do some thing start time is {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},Thread ID is {Thread.CurrentThread.Managed ThreadId},is back ground: {Thread.CurrentThread.IsBackground}");
long result =0;
for(long i =0; i <10000*10000; i++)
{
result += i;
}
Console.WriteLine($"do some thing end time is {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},Thread ID is {Thread.CurrentThread.ManagedT hreadId},is back ground: {Thread.CurrentThread.IsBackground}");
}
注意 :thread 默认是前台线程,启动后⼀定要完成任务的,即使程序关掉(进程退出)也要执⾏完。可以把thread 指定为后台线程,随着进程的退出⽽终⽌。
//false,默认是前台线程,启动后⼀定要完成任务的,即使程序关掉(进程退出)也要执⾏完。
Console.WriteLine(thread.IsBackground);
thread.IsBackground = true;//指定为后台线程。(随着进程的退出⽽退出)
Sleep
Thread.Sleep()会暂停当前线程,,并等待⼀段时间。其实,Thread.Sleep只是放弃时间⽚的剩余时间,让系统重新调度并选择⼀个合适的线程。
在没有其他活动线程的情况下,使⽤Thread.Sleep(0)还是会选上⾃⾝,即连任,系统不会对其做上下⽂切换。
static void Main(string[] args)
{
Stopwatch stopwatch=new Stopwatch();
stopwatch.Start();
Thread.Sleep(0);
stopwatch.Stop();
System.Console.WriteLine(stopwatch.ElapsedMilliseconds);//返回0
}
⽽Thread.Sleep(⼤于0)却让当前线程沉睡了,即使只有1ms也是沉睡了,也就是说当前线程放弃下次的竞选,所以不能连任,系统上下⽂必然发⽣切换。
阻塞
如果线程的执⾏由于某种原因导致暂停,那么就认为该线程被阻塞了。例如在Sleep或者Join等待其他线程结束。被阻塞的线程会⽴即将其处理器的时间⽚⽣成给其他线程,从此就不再消耗处理器时间。
Thread的回调⽤法:
Thread没有像Framework中的delegate的回调⽤法,如果需要回调得⾃动动⼿改造:
private void CallBack(Action action, Action calback)
{
Thread thread = new Thread(()=>{ action(); calback();});
thread.Start();
}
//⽆参⽆返回值
CallBack(()=> Console.WriteLine("好吗?"),()=> Console.WriteLine("好的!"));
复制代码
复制代码
private Func<T> CallBackReturn<T>(Func<T> func)
{
T t = default(T);
Thread thread = new Thread(()=>
{
t = func();
});
thread.Start();
return()=>
{
thread.Join();
return t;
};
}
//带返回值得⽤法
Func<int> func = CallBackReturn<int>(()=> DateTime.Now.Second);
Console.WriteLine("线程未阻塞");
int result = func.Invoke();
Console.WriteLine("result:"+ result);
ThreadPool 线程池
Thread的功能太过强⼤,像我这样的⼩⽩是⽤不好的(之前在项⽬中⼤量使⽤Thread的API,出现了许多意想不到的bug)。线程池中的线程在同⼀操作中可以被重复利⽤。
//开启多线程
ThreadPool.QueueUserWorkItem(n => DoSomeThing(“ThreadPool”));
个⼈觉得尽量不要阻塞线程池的线程,因为线程池⾥的线程数量是有限的,当线程池中没有线程可⽤时,会出现死锁。如果⾮要等待,⽤法如下:
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(n =>
{
DoSomethingLong("ThreadPool");
manualResetEvent.Set();
});
//等待线程完成
manualResetEvent.WaitOne();
Task
Task是基于ThreadPool的基础上做的封装,属于线程池中的线程。
Task启动多线程的⽅式:
⽅式⼀:指定任务的开始时机
///<summary>
///使⽤Task或Task<T>创建任务,需指定任务的开始时机(任务调度)。
///</summary>
public static void Demo1()
{
Task task = new Task(()=>
{
Thread.Sleep(3000);
Console.WriteLine($"Current thread id is {Thread.CurrentThread.ManagedThreadId}");
});
一个线程可以包含多个进程task.Start();//任务调度(开始任务)
Console.WriteLine($"Current thread name is {Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"当前任务状态:{task.Status}");
task.Wait();//等待任务执⾏完成
Console.WriteLine($"当前任务状态:{task.Status}");
}
⽅式⼆:⼀步完成多线程的创建和启动
///<summary>
///使⽤Task.Run()⽅法⼀步完成多线程的创建和启动(当前线程⽴即准备启动任务)。
/
//<remark>
///如果不需要对任务的创建和调度做更多操作,Task.Run()⽅法是创建和启动任务的⾸选⽅式。
///</remark>
///</summary>
public static void Demo2()
{
Task task = Task.Run(()=>{ Thread.Sleep(3000); Console.WriteLine($"Current thread id is {Thread.CurrentThread.ManagedThreadId}");}); task.Wait();//等待,直到任务完成
}
⽅式三:需要想多线程任务传递状态参数
///<summary>
/// Task和Task<TResult>都有静态属性Factory,它返回默认的实例TaskFactory.
///使⽤Task.Factory.StartNew()⽅法也可以⼀步完成任务的创建和启动。
///当前需要向任务传递⼀个状态(参数)。可以使⽤此⽅法。
///</summary>
public static void Demo3()
{
Task[] tasks = new Task[10];
for(int i =0; i < tasks.Length; i++)
{
tasks[i]= Task.Factory.StartNew((obj)=>
{
CustomData data = obj as CustomData;
data.ThreadId = Thread.CurrentThread.ManagedThreadId;
}, new CustomData { CreationTime = DateTime.Now.Ticks, Index = i});
}
//以阻塞当前线程的⽅式,等待所以⼦线程的完成
Task.WaitAll(tasks);
foreach (var task in tasks)
{
//通过任务的AsyncState属性,可以获取任务状态(提供给任务的参数).
var data = task.AsyncState as CustomData;
Console.WriteLine(JsonConvert.SerializeObject(data));
}
}
//Task.Factory.StartNew()调⽤⽆返回值的任务
//Task<TResult>.Factory.StartNew()调⽤有返回值的任务
Task
public static void Demo4()
{
Task<Double>[] tasks ={
Task<Double>.Factory.StartNew(()=> DoComputation(1.0)),
Task<Double>.Factory.StartNew(()=> DoComputation(100.0)),
Task<Double>.Factory.StartNew(()=> DoComputation(1000.0))};
var results = new Double[tasks.Length];
Double sum=0;
for(int i =0; i < tasks.Length; i++)
{
//Task<TResult>.Result属性包含任务的计算结果,如果在任务完成之前调⽤,则会阻塞线程直到任务完成
results[i]= tasks[i].Result;
Console.Write("{0:N1} {1}", results[i],
i == tasks.Length -1 ? "= ":"+ ");
sum+= results[i];
}
Console.WriteLine("{0:N1}",sum);
}
private static Double DoComputation(Double start)
{
Double sum=0;
for(var value = start; value <= start +10; value +=.1)
sum+= value;
return sum;
}
Task的常⽤API
WaitAny和WaitAll,会阻塞当前线程(主线程)的执⾏:
List<Task> tasks = new List<Task>();
tasks.Add(Task.Run(()=> DoSomeThing("Task1")));
tasks.Add(Task.Run(()=> DoSomeThing("Task2")));
tasks.Add(Task.Run(()=> DoSomeThing("Task3")));
//阻塞当前线程的执⾏,等待任意⼀个⼦线程任务完成后继续往下执⾏
Task.WaitAny(tasks.ToArray());
//阻塞当前线程的执⾏,等待所有⼦线程任务完成后继续往下执⾏
Task.WaitAll(tasks.ToArray());
WhenAll和WhenAny,是通过返回⼀个Task 对象的⽅式,来达到⾮阻塞式的等待
//不阻塞当前线程的执⾏,等待所有⼦线程任务完成后,异步执⾏后续的操作
Task.WhenAll(tasks).ContinueWith(t =>
{
Console.WriteLine($"不阻塞,{Thread.CurrentThread.ManagedThreadId}");
});
//⼯⼚模式的实现
Task.Factory.ContinueWhenAll(tasks.ToArray(), s =>
{
Console.WriteLine("不阻塞"+ s.Length);
});
ContinueWith,是⼀个实例⽅式,并且返回Task实例,所以可以使⽤这种链式结构来完成按顺序执⾏。
public static void Demo8()
{
var task = Task.Factory
.StartNew(()=>{ Console.WriteLine("1");return10;})
.ContinueWith(i =>{ Console.WriteLine("2");return i.Result +1;})
.ContinueWith(i =>{ Console.WriteLine("3");return i.Result +1;});
Console.WriteLine(task.Result);
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论