异步编程。在学习使用async/await的时候,很多文档包括msdn都刻意提到async/await关键字不会创建新的线程,用async关键字写的函数中的代码都在调用线程中执行。这里是最容易混淆的地方,严格意义上这个说法不准确,异步编程必然是多线程的。msdn文档里提到的不会创建新线程是指async函数本身不会直接在新线程中运行。通过上面的分析,我们知道本质上是await调用的异步函数执行完成后回调状态机的MoveNext来执行余下未执行完成的代码,await调用的异步函数必然在某个地方——也许是嵌套了很深的一个地方——启动了一个新的工作线程来完成导致我们要使用异步调用的耗时比较长的工作,比如网络内容读取。
到这里已经非常清楚了:异步执行函数首先创建状态机的实例,因为状态机类是Struct类型,不需要new;然后,设置相关属性,状态机的初始状态值被设置为-1,符合之前期望的范围;最后,启动状态机,Start方法内部会调用一次MoveNext,运行结束后返回Task。
多个async函数之间的调用,就是多个状态机的组合运行。
并行编程主要为:开启线程,线程结果返回,线程中止,线程中的异常处理等。
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace YieldReturn
{
class Program
{
static void Main()
{
new Thread(Go).Start(); // .NET 1.0开始就有的
Task.Factory.StartNew(Go); // .NET 4.0 引入了 TPL
Task.Run(new Action(Go)); // .NET 4.5 新增了一个Run的方法
}
public static void Go()
{
Console.WriteLine("我是另一个线程");
}
}
}
/*创建Thread的实例之后,需要手动调用它的Start方法将其启动。但是对于Task来说,StartNew和Run的同时,既会创建新的线程,并且会立即启动它。*/
如何创建一个线程:
/*第一步:编写入口函数*/
private void EntryPointMethod(){}
/*第二步:创建入口委托*/
ThreadStart entryPoint=new ThreadStart(EntryPointMethod);
/*第三步:创建线程*/
Thread WorkThread=new Thread(entryPoint);

线程状态:
1. 线程创建后将处于UnStarted状态,直到调用了Start()方法--调用后并不一定会立即执行,还要看线程的时间片是否可用。
2. 使线程由Running状态改变为WaitSleepJoin状态的方法:
01. 保持线程同步;
02. 调用Sleep()方法,在睡眠时间完成后,回到Running状态
03. 调用Join()方法。
3. 当线程处于Running时调用Suspend()方法(会继续执行少量指令,确保线程安全的情况下挂起线程)可使状态变为SuspendedRequested。需使用Resume()方法恢复到Running状态。
4. IsBackGround属性--后台线程不妨碍前台程序终止。一旦一个应用程序的所有前台程序终止,CLR即调用任何一个存活的Abort()方法彻底终止应用程序。
多个线程同时运行演示:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MultiThread
{
class Program
{
static void Main(string[] args)
{
/*线程A*/
Thread ThreadA = new Thread(delegate()
{
for (var i = 0; i < 1000000; i++)
{
Console.Write('A');
}
});
/*线程B*/
Thread ThreadB = new Thread(delegate()
{
for (var i = 0; i < 1000000; i++)
{
Console.Write('B');
}
});
/*设置线程优先级*/
ThreadA.Priority = ThreadPriority.AboveNormal;
ThreadB.Priority = ThreadPriority.BelowNormal;
/*启动线程*/
ThreadA.Start();
ThreadB.Start();
/*主线程*/
for(var i=0;i<1000000;i++)
{
if(i%10000==0)
{
Console.Write('M');
}
}
}
}
}

线程同步:共享临界资源时使用
1. 交互锁
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadSynchronization
{
class Program
{
/// <summary>
/// 线程同步用于处理临界资源
/// </summary>
/// <param name="args"></param>
private static char buffer;
private static long numberOfUsedSpace = 0;
static void Main(string[] args)
{
Thread Writer = new Thread(delegate()
{
string sentence = "无可奈何花落去,似曾相识燕归来";
for (var i = 0; i < sentence.Length; i++)
{
/*写入数据前,检查缓存区是否已满;已满-等待,直到被读取*/
while(Interlocked.Read(ref numberOfUsedSpace)==1)
{
Thread.Sleep(10);
}
buffer = sentence[i]; //向缓冲区写入数据
Interlocked.Increment(ref numberOfUsedSpace); //写入数据入,将缓冲区状态标记为满
}
});
Thread Reader = new Thread(delegate()
{
for (var i = 0; i < 15; i++)
{
while(Interlocked.Read(ref numberOfUsedSpace)==0)
{
Thread.Sleep(10);
}
char ch = buffer;
Console.Write(ch);
Interlocked.Decrement(ref numberOfUsedSpace); //写入数据入,将缓冲区状态标记为满
}
});
/*启动线程*/
Writer.Start();
Reader.Start();
}
}
}

2. 管程(Monitor类)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadSynchronizationWithMonitor
{
class Program
{
/*缓冲区大小*/
private static char buffer;
/*用于同步对象*/
private static object lockForBuffer = new object();
static void Main(string[] args)
{
Thread Writer = new Thread(delegate()
{
string sentence = "无可奈何花落去,似曾相识燕归来";
for (var i = 0; i < sentence.Length; i++)
{
try
{
/*进入临界区*/
Monitor.Enter(lockForBuffer);
/*缓冲区写入数据*/
buffer = sentence[i];
/*唤醒睡眠在临界资源上的线程*/
Monitor.Pulse(lockForBuffer);
/*让当前线程睡眠在临界资源上*/
Monitor.Wait(lockForBuffer);
}
catch (System.Threading.ThreadInterruptedException)
{
Console.WriteLine("thread writer is stopped");
}
finally
{
/*退出临界区*/
Monitor.Exit(lockForBuffer);
}
}
});
Thread Reader = new Thread(delegate()
{
for (var i = 0; i < 15; i++)
{
try
{
/*进入临界区*/
Monitor.Enter(lockForBuffer);
char ch = buffer;
Console.Write(ch);
/*唤醒睡眠在临界资源上的线程*/
Monitor.Pulse(lockForBuffer);
/*让当前线程睡眠在临界资源上*/
Monitor.Wait(lockForBuffer);
}
catch (System.Threading.ThreadInterruptedException)
{
Console.WriteLine("thread reader is stopped");
}
finally
{
/*退出临界区*/
Monitor.Exit(lockForBuffer);
}
}
});
/*启动线程*/
Writer.Start();
Reader.Start();
}
}
}

注意点:
1. Monitor类只能锁定引用类型对象,不能锁定值类型变量。(因为每次调用Monitor.Enter()时,会执行一次装箱操作。获得一个新的Ojbect对象。)
2. 为了确保退出临界区时临界资源得到释放,Monitor类的使用放在try语句中,并在finally块中调用Monitor.Exit()方法。
3. 使用独占锁时,其它线程不能访问该资源,只有Lock语句结束后其它线程才能访问,相当于禁用了应用程序的多线程功能。只有在必要时才设置独占锁
当然,有更简单的实现方式:Lock语句块
using System;
using System.Threading;
namespace ThreadSynchronizationWithMonitor
{
class Program
{
/*缓冲区大小*/
private static char buffer;
/*用于同步对象*/
private static object lockForBuffer = new object();
static void Main(string[] args)
{
Thread Writer = new Thread(delegate()
{
string sentence = "无可奈何花落去,似曾相识燕归来";
for (var i = 0; i < sentence.Length; i++)
{
lock(lockForBuffer)
{
/*缓冲区写入数据*/
buffer = sentence[i];
/*唤醒睡眠在临界资源上的线程*/
Monitor.Pulse(lockForBuffer);
/*让当前线程睡眠在临界资源上*/
Monitor.Wait(lockForBuffer);
}
}
});
Thread Reader = new Thread(delegate()
{
for (var i = 0; i < 15; i++)
{
lock(lockForBuffer)
{
char ch = buffer;
Console.Write(ch);
/*唤醒睡眠在临界资源上的线程*/
Monitor.Pulse(lockForBuffer);
/*让当前线程睡眠在临界资源上*/
Monitor.Wait(lockForBuffer);
}
}
});
/*启动线程*/
Writer.Start();
Reader.Start();
}
}
}

Mutex类(非静态)--只有获得了Mutex对象所属权的线程才能进入临界区
using System;
using System.Threading;
using System.IO;
namespace ThreadSynchronizationWithMutex
{
class Program
{
/*The first Thread*/
Thread ThreadA = new Thread(delegate()
{
Mutex fileMutex = new Mutex(false, "MutexForTimeRecordFile");
string fileName = @"C:/TimeRecord.txt";
for (var i = 0; i <= 10; i++)
{
try
{
/**/
fileMutex.WaitOne();
File.AppendAllText(fileName, "ThreadA:" + DateTime.Now + "\r\n");
}
catch (System.Threading.ThreadInterruptedException)
{
Console.WriteLine("ThreadA is interupte");
}
finally
{
fileMutex.ReleaseMutex();
}
Thread.Sleep(1000);
}
});
static void Main(string[] args)
{
/*The first Thread*/
Thread ThreadB = new Thread(delegate()
{
Mutex fileMutex = new Mutex(false, "MutexForTimeRecordFile");
string fileName = @"C:/TimeRecord.txt";
for (var i = 0; i <= 10; i++)
{
try
{
/**/
fileMutex.WaitOne();
File.AppendAllText(fileName, "ThreadB:" + DateTime.Now + "\r\n");
}
catch (System.Threading.ThreadInterruptedException)
{
Console.WriteLine("ThreadB is interupte");
}
finally
{
fileMutex.ReleaseMutex();
}
Thread.Sleep(1000);
}
});
ThreadB.Start();
System.Diagnostics.Process.Start("MutexA.exe"); //run MutexA.exe
}
}
}
DeadLock-死锁
/*只要2个线程按相同顺序访问临界资源就能防止死锁*/
using System; using System.Threading; namespace DeadLock {
class Program { /*临界资源:叉*/ private static object knife = new object(); /*临界资源:刀*/ private static object fork = new object(); static void Main(string[] args) { Thread GirlThread = new Thread(delegate() { /*男孩和女孩聊天*/ Console.WriteLine("今天的月亮好美啊..."); lock (knife) { GetKnife(); lock (fork) { GetFork(); Eat(); Console.WriteLine("女孩放下fork"); Monitor.Pulse(fork); } Console.WriteLine("女孩放下knife"); Monitor.Pulse(knife); } }); GirlThread.Name = "Girl"; Thread BoyThread = new Thread(delegate() { /*男孩和女孩聊天*/ Console.WriteLine("今天的月亮好美啊..."); lock (fork) { GetFork(); lock (knife) { GetKnife(); Eat(); Console.WriteLine("男孩放下knife"); Monitor.Pulse(knife); } Console.WriteLine("男孩放下fork"); Monitor.Pulse(fork); } }); BoyThread.Name = "Boy"; /*Start Thread*/ GirlThread.Start(); BoyThread.Start(); } /*GetKnife*/ static void GetKnife() { Console.WriteLine(Thread.CurrentThread.Name + "GetKnife"); } /*GetFork*/ static void GetFork() { Console.WriteLine(Thread.CurrentThread.Name + "GetFork"); } /*Eat*/ static void Eat() { Console.WriteLine(Thread.CurrentThread.Name + "Eat"); } } }
线程池
1. 线程的创建是比较占用资源的一件事情.
2. .NET 为我们提供了线程池来帮助我们创建和管理线程。
3. Task是默认会直接使用线程池,但是Thread不会。如果我们不使用Task,又想用线程池的话,可以使用ThreadPool类。
浙公网安备 33010602011771号