//开辟了一个的新的线程Thread
Thread t = new Thread(WryteY);
t.Start();
//主线程在做的一些事
for(int i=0; i<1000; i++)
{
Console.Write("x");
}
void WryteY(object obj)
{
for(int i=0; i < 1000; i++)
{
Console.Write("y");
}
}
上例讲解:
在单核计算机上,操作系统必须为每个线程分配"时间片"来模拟并发,从而导致重复的x和y。
在多核或多处理器计算上,这两个线程可以真正地并行执行(也可能受到计算机其他活动进程的竞争)
// 为main线程起个名字
Thread.CurrentThread.Name = "Main Thread...";
//开辟了一个的新的线程Thread
Thread t = new Thread(WryteY);
t.Name = "Y_Thread...";
t.Start();
Console.WriteLine(Thread.CurrentThread.Name);
//主线程在做的一些事
for(int i=0; i<1000; i++)
{
Console.Write("x");
}
void WryteY(object obj)
{
Console.WriteLine(Thread.CurrentThread.Name);
for(int i=0; i < 1000; i++)
{
Console.Write("y");
}
}
Thread t = new Thread(Go);
t.Start();
t.Join();//会等着线程t执行完毕才会接着下面执行
Console.WriteLine("Thread t has ended!");
void Go(object obj)
{
for(int i = 0; i < 1000; i++)
{
Console.Write("y");
}
}
Demo2:
Thread.sleep()被用来暂停当前线程的执行,会通知线程调度器把当前线程在指定的时间周期内置为wait状态。当wait时间结束,线程状态重新变为Runnable并等待CPU的再次调度执行。所以线程sleep的实际时间取决于线程调度器,而这是由操作系统来完成的。
一个进程在运行态时调用sleep(),进入等待态,睡眠结束以后,并不是直接回到运行态,而是进入就绪队列,要等到其他进程放弃时间片后才能重新进入运行态。所以sleep(1000),在1000ms以后,线程不一定会被唤醒。sleep(0)可以看成一个运行态的进程产生一个中断,由运行态直接转入就绪态。这样做是给其他就绪态进程使用时间片的机会。总之,还是操作系统中运行态、就绪态和等待态相互转化的问题。
Thread thread1 = null;
Thread thread2 = null;
thread1 = new Thread(ThreadProc);
thread1.Name = "Thread1";
thread1.Start();
thread2 = new Thread(ThreadProc);
thread2.Name = "Thread2";
thread2.Start();
void ThreadProc(object obj)
{
Console.WriteLine($"Current thread: {Thread.CurrentThread.Name}");
// 注意ThreadState
if(Thread.CurrentThread.Name != "Thread1" && thread2.ThreadState!=ThreadState.Unstarted)
thread2.Join(); //如果先进来的是thread1,等待thread2执行完毕,去执行thread2的流程
Thread.Sleep(1000); //如果先进来的是thread1,那么此时休眠的是thread2
Console.WriteLine($"Current thread: {Thread.CurrentThread.Name}");
Console.WriteLine($"Thread1:{thread1.ThreadState}");
Console.WriteLine($"Thread2:{thread2.ThreadState}");
}
Demo3: join(超时参数毫秒)
Thread thread1 = null;
Thread thread2 = null;
thread1 = new Thread(ThreadProc);
thread1.Name = "Thread1";
thread1.Start();
thread2 = new Thread(ThreadProc);
thread2.Name = "Thread2";
thread2.Start();
void ThreadProc(object obj)
{
Console.WriteLine($"Current thread: {Thread.CurrentThread.Name}");
// 注意ThreadState
if(Thread.CurrentThread.Name != "Thread1" && thread2.ThreadState!=ThreadState.Unstarted)
if(thread2.Join(4000)) //thread2执行没超过2s则走下面
Console.WriteLine("Thread2 has termminated.");
else
// 起时已过,Thread1将恢复
Console.WriteLine("The timeout has elapsed and Thread1 will resume");
Thread.Sleep(1000);
Console.WriteLine($"Current thread: {Thread.CurrentThread.Name}");
Console.WriteLine($"Thread1:{thread1.ThreadState}");
Console.WriteLine($"Thread2:{thread2.ThreadState}");
}
总结:
如果线程的执行由于某种原因导致暂停,那么就认为该线程被阻塞了。如在Sleep或者通过Join等待其他线程结束。
被阻塞的线程会立即将其处理器的时间片释放,直到满足阻塞条件为止
可以通过ThreadState这个属性来判断线程是否处理被阻塞的状态:
bool blocked = (someThread.ThreadState & ThreadState.WaitSleepJoin) !=0;
ThreadState是一个flags enum,它大部分的枚举值都没有什么有,常用的几个如下:
当遇到下列四种情况的时候,就会解除阻塞
当线程阻塞或解除,操作系统将执行上下文切换。这会产生少量开销,通常为1或2微秒
本地 vs 共享的状态
Local vs Shared State
Local本地独立
Shared共享
Shared共享的数据在多个线程中被使用会出现线程安全问题。
锁定与线程安全
class ThreadSafe
{
static bool _done;
static readonly object _locker = new object();
static void Main()
{
new Thread(Go).Start();
Go();
}
static void Go()
{
lock (_locker)
{
if (!_done)
{
Console.WriteLine("Done");
_done = true;
}
}
}
}
如果你想往线程的启动方法里传递参数,最简单的方式是使用lambda表,在里面使用参数调用方法。
class Program
{
static void Main()
{
Thread t = new Thread(() => Print("Hello from t!"));
t.Start();
}
static void Print(string message)
{
Console.WriteLine(message);
}
}
整个逻辑都放在lambda表达式中
class Program
{
static void Main()
{
new Thread(() =>
{
Console.WriteLine("I'm running on anoter thread!");
Console.WriteLine("This is so easy!");
}).Start();
}
}
使用Lambda表达式可以很简单的给Thread传递参数,但是线程开始后,可以不能小修改了被捕获的变量,可能出现问题如下例
class Program
{
static void Main()
{
for(int i = 0; i < 10; i++)
{
new Thread(()=>Console.Write(i)).Start();
}
}
/*
* i在循环的整个生命周期内指向的同一个同存地址
*/
}
下例解决方案
class Program
{
static void Main()
{
for(int i = 0; i < 10; i++)
{
int temp = i;
new Thread(()=>Console.Write(temp)).Start();
}
}
}
class Program
{
static void Main()
{
new Thread(Go).Start();
}
static void Go() //将异常放在逻辑中
{
try
{
throw null;
}catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
提升线程优先级
p.PriorityClass = ProcessPriorityClass.High;
class Program
{
static void Main()
{
var signal = new ManualResetEvent(false);
new Thread(() =>
{
Console.WriteLine("Waiting for signal......");
signal.WaitOne();//阻塞,等另一个线程开启信号
signal.Dispose();
Console.WriteLine("Got Signal!");
}).Start();
Thread.Sleep(1000); //线程暂停一秒
signal.Set(); //发送信号
}
}
Thread的问题
Task Class
Task Run
class Program
{
static void Main()
{
// 创建了一个任务,默认使用的是线程池
// 主线程结束,所有后台线程都将关闭
Task.Run(() => Console.WriteLine("Foo"));
}
}
Wait 等待
class Program
{
static void Main()
{
Task task = Task.Run(() =>
{
Thread.Sleep(3000);
Console.WriteLine("Foo");
});
Console.WriteLine(task.IsCompleted); //false
task.Wait(); // 阻塞直至task完成操作
Console.WriteLine(task.IsCompleted); //true
}
}
长时间运行的任务(Long-running tasks)
class Program
{
static void Main()
{
Task<int> task = Task.Run(() =>
{
Console.WriteLine("Foo");
return 3;
});
int result = task.Result; // 如果task没有完成就阻塞
Console.WriteLine(result);
}
}
class Program
{
static void Main()
{
Task task = Task.Run(() => { throw null; });
try
{
task.Wait();
}catch(AggregateException aex)
{
if(aex.InnerException is NullReferenceException)
{
Console.WriteLine("Null");
}
else
{
throw;
}
}
}
}
异常与“自治”的Task
未观察到的异常
awaiter
如果发生故障
非泛型task
class Program
{
static void Main()
{
Task<int> primeNumberTask = Task.Run(() =>
Enumerable.Range(2, 3000000).Count(n =>
Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)));
var awaiter = primeNumberTask.GetAwaiter();
// 你执行完了我立即执行回调
awaiter.OnCompleted(() =>
{
// 有问题,异常会抛出,不直接拿结果就是为了如果有异常可以捕获
int result = awaiter.GetResult();
Console.WriteLine(result);
});
Console.ReadLine();
}
}
使用TaskCompletionSource
public class TaskCompletionSource<TResult>
{
public void SetResult(TResult result);
public void SetException(Exception exception);
public void SetCanceled();
......
public bool TrySetResult(TResult result);
public bool TrySetException(Exception exception);
public bool TrySetCanceled();
public bool TrySetCanceled(CancellationToken cancellationToken);
}
class Program
{
static void Main()
{
var tcs = new TaskCompletionSource<int>();
new Thread(() =>
{
Thread.Sleep(5000);
tcs.SetResult(42);
})
{
IsBackground = true
}.Start();
Task<int> task = tcs.Task;
Console.WriteLine(task.Result);
}
}
同步、异步
首先我们得跳出我们的固有思维,我们生活中常说的同步就是一起执行,但是计算机中同步却是另外的概念!
举个例子,你家里只有一个洗手间,但是你跟你爸都想上厕所,怎么办?只能一个一个来,没有问题,问题
在于你在等你爸从洗手间出来的这段时间里,你是站在门口一直等还是去干其他的?那么同步跟异步的概念就可以这样解释:
同步:你在门口一直等,你爸用完了你进去,要是你妈也来了,也是站在后面等,就像排队挂号一样。把人当做线程,把洗手间当做资源,
同一时间多个线程申请使用一个资源,要是在后面排队就是线程同步。
那异步呢?很简单,你申请了想要用洗手间,但是你去看电视了,这个就叫异步,等有人告诉你洗手间空出来了,你再决定要不要去,当然你肯定得去,
只是你可以自己决定什么时候去。一个线程同一时间访问多个资源,便称之为异步
async 和 await关键字可以让你写出和同步代码一样简洁且结构相同的异步代码
await关键字简化了附加continuation的过程
其结构如下:
var result = await expression;
statement(s);
它的作用相当于:
var awaiter = expression.GetAwaiter();
awaiter.OnCompleted(()=>
{
var result = awaiter.GetResult();
statement(s);
}
);
class Program
{
static async Task Main()
{
// 方法内有await,方法必须加上async
// async方法无返回值必须加上Task
await DisplayPrimesCountAsync();
}
// async无返回值要加上Task
static async Task DisplayPrimesCountAsync()
{
int result = await GetPrimesCountAsync(2, 1000000);
Console.WriteLine(result);
}
// Task泛型中的类型就是返回值的类型
static Task<int> GetPrimesCountAsync(int start, int count)
{
return Task.Run(() =>
ParallelEnumerable.Range(start, count).Count(n =>
Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)));
}
}
async修饰符
异步方法如何执行
如下两个方法基本一样:
void DisplayPrimesCount()
{
var awaiter = GetPrimesCountAsync(2, 1000000).GetAwaiter();
// 获得延续体继续执行,以获取到当前线程的环境
awaiter.OnCompleted(()=>
{
int result = awaiter.GetResult();
Console.Writeline(result);
});
}
static async Task DisplayPrimesCountAsync()
{
// await会获得一个延续体,回来获得当前线程的环境
int result = await GetPrimesCountAsync(2, 1000000);
Console.Writeline(result)
}
捕获本地状态
async void DisplayPrimeCounts()
{
for (int i=0; i<10; i++)
// GetPrimesCountAsync是一个异步方法
// await每次调用完异步方法都回加到这里来而且能捕获到本地变量i
Console.WriteLine(await GetPrimesCountAsync(i*1000000+2, 1000000);
}
await之后在那个线程上执行
对于任何异步函数,你可以使用Task替代void作为返回类型,让该方法成为更有效的异步(可以进行await)
class Program
{
static async Task Main()
{
await PrintAnswerToLife();
}
static async Task PrintAnswerToLife()
{
await Task.Delay(1000);
int answer = 21 * 2;
Console.WriteLine(answer);
}
}
并不需要在方体中显式的返回Task。编译器会生成一个Task(当方法完成或发生异常时),这使得创建异步的调用链非常方便。
class Program
{
static async Task Main()
{
await Go();
}
static async Task Go()
{
await PrintAnswerToLife();
Console.WriteLine("Done");
}
static async Task PrintAnswerToLife()
{
await Task.Delay(1000);
int answer = 21 * 2;
Console.WriteLine(answer);
}
}
编译器会对返回Task的异步函数进行扩展,使其成为当发送信号或发生故障时使用TaskCompletionSource来创建Task代码。
因此,当返回Task的异步方法结束的时候,执行就会跳回到对它进行await的地方。(通过continuation)
下面这段代码
static async Task PrintAnswerToLife()
{
await Task.Delay(1000);
int answer = 21 * 2;
Console.WriteLine(answer);
}
编译器会给我们翻译成:
static Task PrintAnswerToLife()
{
var tcs = new TaskCompletionSource<Object>();
var awaiter = Task.Delay(1000).GetAwaiter();
awaiter.OnCompleted(async () =>
{
try
{
awaiter.GetResult();
int answer = 21 * 2;
Console.WriteLine(answer);
tcs.SetResult(null);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
});
return tcs.Task;
}
}
返回Task
如果方法体返回TResult,那么异步方法就可以返回Task
其原理就是给TaskCompletion发送信号带有值,而不是null
class Program
{
static async Task Main()
{
int result = await GetAnswerToLife();
Console.WriteLine(result);
}
static async Task<int> GetAnswerToLife()
{
await Task.Delay(5000);
int answer = 21 * 2;
return answer;
}
}
异步Lambda表达式
匿名方法(包括Lambda表达式),通过使用asyn也可以变成异步方法
class Program
{
static async Task Main()
{
Func<Task> unnamed = async () =>
{
await Task.Delay(1000);
Console.WriteLine("unnamed");
};
await NamedMethod();
await unnamed();
}
static async Task NamedMethod()
{
await Task.Delay(1000);
Console.WriteLine("NamedMethod");
}
}
异步Lambda表达式也可以返回Task
class Program
{
static async Task Main()
{
Func<Task<int>> unnamed = async () =>
{
await Task.Delay(1000);
return 123;
};
int result =await unnamed();
Console.WriteLine(result);
}
}
编写完全没有await的异步方法也是合法的,但是编译器会发出警告
async Task
另外一种可以达到相同结果的方式是:使用Task.FromResult,它会返回一个已经设置好信号的Task
Task
TAB Task-Based Asynchronous Pattern 基于Task的异步模式(TAP)