时间:2021年04月28日 | 作者 : aaronyang | 分类 : .NET CORE | 浏览: 982次 | 评论 0 人
进程拥有资源(内存+线程)线程逐句执行代码。默认 每个进程只有一个线程,执行多任务会导致问题。线程还负责跟踪当前经过身份验证的用户,以及语言和区域的规则。
Windows和大多其他OS使用了抢夺式多任务处理,从而模拟了并行执行任务。这种机制,可将CPU时间分配给各个线程,一个接一个地为每个线程分配时间片,当前线程在时间片结束时挂起,然后CPU允许另一个线程运行时间片。
Windows切换线程,会保存线程的上下文,并重新加载线程队列中先前保存的下一个线程的上下文。这需要时间和资源才能完成。
线程有 Priority和ThreadState属,此外还有ThreadPool类,用于管理后台工作线程。
更多: 链接
线程可能需要竞争并等待对共享资源的访问,比如变量、文件和数据库对象。
线程不是越多越好,要看你什么场景了。
sizeof() 测量类型的 大小
System.Diagnostics有很多监控代码的有用类型
比如Stopwatch统计耗时
Restart() 将经过的的时间重置为0,然后启动计时器
Stop() 停止
Elapsed 时间存储为TimeSpan格式
ElapsedMilliseconds 时间存储为 毫秒为单位的long类型
Process类
VirtualMemorySize64 显示为进程分配的虚拟内存量(字节为单位)
WorkingSet64 显示为进程分配的物理内存量(字节为单位)
接下来新建一个Chapter13文件夹,然后打开vs2019开始新建一个MonitoringLib的net5类库,新建一个MonitoringApp的net5的控制台,控制台 引用 这个MonitoringLib库.不会新建的参考这篇博客
编写代码:测试10000个整数类型的数组
using System; using static System.Console; using static System.Diagnostics.Process; using System.Diagnostics; namespace MonitoringLib { public static class Recorder { static Stopwatch timer = new Stopwatch(); static long bytesPhysicalBefore = 0; static long bytesVirtualBefore = 0; public static void Start() { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); bytesPhysicalBefore = GetCurrentProcess().WorkingSet64; bytesVirtualBefore = GetCurrentProcess().VirtualMemorySize64; timer.Start(); } public static void Stop() { timer.Stop(); long bytesPhysicalAfter = GetCurrentProcess().WorkingSet64; long bytesVirtualAfter = GetCurrentProcess().VirtualMemorySize64; Console.WriteLine("{0:N0} 物理内存字节使用了.",bytesPhysicalAfter-bytesPhysicalBefore); Console.WriteLine("{0:N0} 虚拟内存字节使用了." ,bytesVirtualAfter-bytesVirtualBefore); Console.WriteLine("{0} 时间",timer.Elapsed); Console.WriteLine("{0:N0} 毫秒", timer.ElapsedMilliseconds); } } }
控制台
===============www.ayjs.net=================
using MonitoringLib; using System; using System.Linq; namespace MonitoringApp { class Program { static void Main(string[] args) { Console.WriteLine("处理中,请稍等..."); Recorder.Start(); var largeArray = Enumerable.Range(1, 10_000).ToArray(); System.Threading.Thread.Sleep(new Random().Next(5,10)*1000); Recorder.Stop(); } } }
测试 50000个int,然后使用string和StringBuilder类 用逗号连接起来
客户端测试代码:
#region DEMO2 var number2 = Enumerable.Range(1, 50_000).ToArray(); Recorder.Start(); Console.WriteLine("使用string"); string s=""; for (int i = 0; i < number2.Length; i++) { s += number2[i] + ","; } Recorder.Stop(); Recorder.Start(); Console.WriteLine("使用stringbuilder"); StringBuilder sb = new StringBuilder(); for (int i = 0; i < number2.Length; i++) { sb.Append(number2[i]); sb.Append(","); } Recorder.Stop(); #endregion
string的大约使用了14MB的物理内存和 31M的虚拟内存,耗时1.428秒
stringbuilder 大约8k物理内存,没有使用虚拟内存,耗时1.429毫秒,差距不大
为了理解多个任务同时运行,控制台添加3个方法。
先看正常写法
static void MethodA() { Console.WriteLine("执行方法A"); Thread.Sleep(3000); Console.WriteLine("执行完毕A"); } static void MethodB() { Console.WriteLine("执行方法B"); Thread.Sleep(2000); Console.WriteLine("执行完毕B"); } static void MethodC() { Console.WriteLine("执行方法C"); Thread.Sleep(1000); Console.WriteLine("执行完毕C"); }
执行:
#region DEMO3 var timer = Stopwatch.StartNew(); //同步执行就是,一个一个的执行 MethodA(); MethodB(); MethodC(); Console.WriteLine("{0:N0} 毫秒", timer.ElapsedMilliseconds); #endregion
关于Thread,.NET第一个版本就可以用呢
.NET Framework4.0 引入了Task,Task类是线程包装器,可以更容易的创建和管理线程。通过管理任务中包装的多个线程,可以实现代码的异步执行。
每个Task实例都有Status和CreationOptions属性,还有ContinueWith方法,该方法可以使用TaskContinuationOptions枚举进行自定义,也可以使用TaskFactory类进行管理
DEMO4
#region DEMO4 var timer4 = Stopwatch.StartNew(); //异步执行就是,同时执行 Task taskA = new Task(MethodA); taskA.Start(); Task taskB = Task.Factory.StartNew(MethodB); Task taskC = Task.Run(MethodC); Console.WriteLine("{0:N0} 毫秒", timer4.ElapsedMilliseconds); #endregion Console.ReadKey();
如果不加ReadKey()代码,控制台到最后一行,程序就退出了。
你不知道啥时候执行完的
有时候 需要等待任务完成后才能继续。为此,可以对Task实例 调用Wait方法,或者对任务数组调用WaitAll或者WaitAny静态方法
t.Wait() 等待名为t的Task实例完成执行
Task.WaitAny(Task[]) 等待数组中的任何任务完成执行
Task.WaitAll(Task[]) 等待数组中的所有任务完成执行
测试代码:
#region DEMO5 var timer5 = Stopwatch.StartNew(); Task taskA = new Task(MethodA); taskA.Start(); Task taskB = Task.Factory.StartNew(MethodB); Task taskC = Task.Run(MethodC); Task[] tasks = { taskA , taskB, taskC}; Task.WaitAll(tasks); Console.WriteLine("{0:N0} 毫秒", timer5.ElapsedMilliseconds); #endregion
这个不需要最后的ReadKey()代码了,C先执行完。
这3个线程同时执行他们的代码,并且任意顺序启动。实际使用的CPU对结果有很大的影响。
考虑到其中一个任务通常依赖于另一个任务的输出,为了处理以上场景,需要定义延续任务。
创建一个方法模拟web调用,返回一个金额,第一个方法返回的结果需要传入第二个方法的输入。
static decimal CallWeb() { Console.WriteLine("开始调用web服务"); Thread.Sleep((new Random()).Next(2000, 4000)); Console.WriteLine("结束调用web服务"); return 199.98M; } static string CallStoreProcedure(decimal amout) { Console.WriteLine("开始调用CallStoreProcedure"); Thread.Sleep((new Random()).Next(2000, 4000)); Console.WriteLine("结束调用CallStoreProcedure"); return "有12个产品大于"+amout+"金额"; }
测试代码:
#region DEMO6 var timer6 = Stopwatch.StartNew(); var taskcall = Task.Factory.StartNew(CallWeb).ContinueWith(x => CallStoreProcedure(x.Result)); Console.WriteLine($"结果:{taskcall.Result}"); Console.WriteLine("{0:N0} 毫秒", timer6.ElapsedMilliseconds); #endregion
嵌套任务是 在另一个任务中创建的任务。
子任务是 必须在允许父任务完成之前完成的嵌套任务。
创建两个方法,其中一个方法用来启动一个任务以运行另一个任务。
#region DEMO7 var timer7 = Stopwatch.StartNew(); var outerTask = Task.Factory.StartNew(OuterMethod); outerTask.Wait(); Console.WriteLine("{0:N0} 毫秒", timer7.ElapsedMilliseconds); #endregion } static void OuterMethod() { Console.WriteLine("外方法开始执行..."); Task t = Task.Factory.StartNew(InnerMethod); Console.WriteLine("外方法执行结束"); } static void InnerMethod() { Console.WriteLine("嵌套法开始执行..."); Thread.Sleep(2000); Console.WriteLine("嵌套方法执行结束"); }
注意,虽然要等待外部的任务完成,但 内部任务不必也完成。实际上,外部任务可能已经完成,内部都没完成,就结束了。
修改OuterMethod代码如下:
static void OuterMethod() { Console.WriteLine("外方法开始执行..."); Task t = Task.Factory.StartNew(InnerMethod,TaskCreationOptions.AttachedToParent); Console.WriteLine("外方法执行结束"); }
查看结果,注意内部的任务必须在外部任务可以完成之前完成
里面的任务没有完成,外面的任务必须等待里面的完成才结束
多个线程 访问同一个变量或资源,会导致问题,就要考虑线程安全。
实现线程安全的最简单机制,是使用对象变量作为标志或指示灯,以指示共享资源何时应用了独占锁。
介绍两个可用于同步访问资源的类型
Monitor 用于防止多个线程在同一进程中同时访问资源的标志
Interlocked 用于在CPU级别操作简单数字类型的对象
新建两个方法,分别异步执行,然后共同抢Message这个变量资源
static Random r = new Random(); static string Message; static void MethodSyncA() { for (int i = 0; i < 5; i++) { Thread.Sleep(2000); Message += "A"; Console.Write("."); } } static void MethodSyncB() { for (int i = 0; i < 5; i++) { Thread.Sleep(r.Next(2000)); Message += "B"; Console.Write("."); } }
#region DEMO8 Console.WriteLine("请等待..."); Stopwatch stopwatch8 = Stopwatch.StartNew(); Task ta = Task.Factory.StartNew(MethodSyncA); Task tb = Task.Factory.StartNew(MethodSyncB); Task.WaitAll(new Task[] { ta, tb }); Console.WriteLine(); Console.WriteLine($"{Message}"); Console.WriteLine("{0:N0} 毫秒", stopwatch8.ElapsedMilliseconds); #endregion
接下来
分别在MethodSyncA方法和MethodSyncB方法的for循环外加lock语句
static object conch = new object(); static void MethodSyncA() { lock (conch) { for (int i = 0; i < 5; i++) { Thread.Sleep(2000); Message += "A"; Console.Write("."); } } } static void MethodSyncB() { lock (conch) { for (int i = 0; i < 5; i++) { Thread.Sleep(r.Next(2000)); Message += "B"; Console.Write("."); } } }
死锁往往发生在有两个或者多个共享资源时
线程X锁定conchA
线程Y锁定conchB
线程X试图锁定conchB,但被阻塞,因为线程Y已经锁定了conchB
线程Y试图锁定conchA,但被阻塞,因为线程X已经锁定了conchA
防止死锁的有效办法是在 尝试获取锁时指定超时,为此,必须手动使用Monitor类而不是使用lock语句。
修改代码:
结果跟上一个demo的效果差不多,但是避免了潜在的死锁
只有在能够编写 避免潜在死锁的代码时才使用lock关键字。如果无法避免,则始终使用Monitor.TryEnter语句并且结合 try-finally,这样就可以提供超时,如果出现死锁,其中一个线程就会退出死锁。
===============www.ayjs.net=================
C#的++不是原子的。递增一个整数需要执行以下三个操作:
1 将值从实例变量加载到寄存器中
2 增加值
3 将值存储在实例变量中
执行前两个操作后,线程可能被抢占。然后,另一个线程可以执行所有这三个操作。当第一个线程继续执行时,将覆盖实例变量的值,第二个线程执行的的增减效果将丢失!
名为Interlocked的类型可以用来对值类型执行原子操作,比如整数和浮点数。
声明另一个共享资源Counter,用于计算发生了多少操作
然后在for语句内部,在修改字符串之后,安全地增加计数器。
static int Counter; static Random r = new Random(); static string Message; static object conch = new object(); static void MethodSyncA() { try { Monitor.TryEnter(conch, TimeSpan.FromSeconds(15)); for (int i = 0; i < 5; i++) { Thread.Sleep(2000); Message += "A"; Interlocked.Increment(ref Counter); Console.Write("."); } } finally { Monitor.Exit(conch); } } static void MethodSyncB() { try { Monitor.TryEnter(conch, TimeSpan.FromSeconds(15)); for (int i = 0; i < 5; i++) { Thread.Sleep(r.Next(2000)); Message += "B"; Interlocked.Increment(ref Counter); Console.Write("."); } } finally { Monitor.Exit(conch); } }
输出
C#
效果:
输出了10,增减效果没有错误,换成++尝试
也没错误,只是说可能会错误
因为在conch保护了由conch锁定所在的代码块中访问的所有共享资源,因此用++运算都可以,没必要用Interlocked
Monitor和Interlocked时互斥锁,他们简单有效,还有更高级的类型
ReaderWriterLock和ReaderWriterLockSlim
以读取模式运行多个线程,其中一个线程允许写入模式运行,并且独占锁的所有权;而另一个线程允许以 可升级的读取模式进行读取访问,在这个线程中,可以升级到写入模式,而不必放弃对资源的读取访问权限。
Mutex
与Monitor一样,提供对共享资源的独占访问,但也可用于进程间同步
通过定义插槽来限制可以i并发访问资源或资源池的线程数量
AutoResetEvent和ManualResetEvent
事件等待句柄允许线程通过相互发送信息和等待彼此的信号来同步活动
C#5 引入了两个关键字简化Task类型的使用
async和await
先理解为什么要引入这两个,后面16章和20章实践
提高控制应用程序的响应能力
控制台存在的限制,只能在标记为async的方法中使用await关键字,C#7及更早版本不允许把Main方法标记为async,C#7.1就支持了
新建一个AsyncConsole
using System; using System.Net.Http; using static System.Console; using System.Threading.Tasks; namespace AsyncConsole { class Program { static async Task Main(string[] args) { var client = new HttpClient(); HttpResponseMessage res = await client.GetAsync("http://www.baidu.com"); WriteLine("百度网站首页有{0}字节",res.Content.Headers.ContentLength); } } }
构建Web应用程序,web服务和带有GUI的应用程序(WPF或者 xamarin等),程序员的工作会变得更加复杂
原因之一,对于GUI应用程序,有个特殊的界面(UI)线程
在GUI工作时两条规则:
不要在UI线程上执行长时间运行的任务
除UI线程外,不要在任何线程上访问UI元素
所以编写代码就比较多,在C#5后续版本,使用async和await关键字。他们允许继续编写代码,就像代码是同步的一样,代码更简洁,但是在底层,C#编译器创建了复杂的状态机,并跟踪正在运行的线程。
DbContext<T> AddAsync AddRangeAsync FindAsync和SaveChangeAsync
DbSet<T> AddAsync AddRangeAsync ForEachAsync SumAsync ToListAsync ToDictionaryAsync AverageAsync CountAsync
HttpClient GetAsync PostAsync PutAsync DeleteAsync SendAsync
StreamReader ReadAsync ReadLineAsync ReadToEndAsync
StreamWriter WriteAsync WriteLineAsync FlushAsync
每当看到一个以Async结尾的方法时,就检查这个方法是否返回Task或者Task<T>,如果是,那么应该使用这个方法而不是使用不以Async作为后缀的方法,记住使用await关键字调用该方法,并使用async关键字进行修饰。
在C#5中,只能在try块中使用await关键字,不能在catch块中使用,在C#6后续版本,在try和catch块都可以使用await关键字。
在C#8.0和NC3.0之前,await关键字只能用于返回标量值的任务。
using System; using System.Net.Http; using static System.Console; using System.Threading.Tasks; using System.Collections.Generic; namespace AsyncConsole { class Program { static async Task Main(string[] args) { //var client = new HttpClient(); //HttpResponseMessage res = await client.GetAsync("http://www.baidu.com"); //WriteLine("百度网站首页有{0}字节",res.Content.Headers.ContentLength); await foreach (int number in GetNumbers()) { Console.WriteLine($"Number:{number}"); } } static async IAsyncEnumerable<int> GetNumbers() { var r = new Random(); System.Threading.Thread.Sleep(r.Next(1000, 2000)); yield return r.Next(0,101); System.Threading.Thread.Sleep(r.Next(1000, 2000)); yield return r.Next(0, 101); System.Threading.Thread.Sleep(r.Next(1000, 2000)); yield return r.Next(0, 101); } } }
线程和线程化:https://docs.microsoft.com/zh-cn/dotnet/standard/threading/threads-and-threading
深度理解async关键字:https://docs.microsoft.com/zh-cn/dotnet/standard/async-in-depth
await关键字:https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/await
.NET并行编程;https://docs.microsoft.com/zh-cn/dotnet/standard/parallel-programming/
同步概率:https://docs.microsoft.com/zh-cn/dotnet/standard/threading/overview-of-synchronization-primitives
===============www.ayjs.net=================
抖音:wpfui 工作wpf
目前在合肥企迈科技公司上班,加我QQ私聊
2023年11月网站停运,将搬到CSDN上
AYUI8全源码 Github地址:前往获取
杨洋(AaronYang简称AY,安徽六安人)和AY交流
高中学历,2010年开始web开发,2015年1月17日开始学习WPF
声明:AYUI7个人与商用免费,源码可购买。部分DEMO不免费
查看捐赠AYUI7.X MVC教程 更新如下:
第一课 第二课 程序加密教程
额 本文暂时没人评论 来添加一个吧
发表评论