线程不是越多越好,线程有时间和空间上的开销,所以我们需要省着用。
现在实际开发中直接用thread的不多,因为它较为底层,很多程序员用不好。
让thread得到更好的使用,提高利用率,减少不必要的创建和销毁。
在老版本的.NET Framework时代,只有一个全局队列,存在大量的锁竞争。
.NET Core中加入了本地队列,加入了本地队列,降低了锁竞争,并提高了线程的利用率。
具体实现思路是:
(1)每个线程优先从本地队列中取任务干活;
(2)如果本地队列中没有任务了,就从全局队列中取任务干活;
(3)当全局任务队列里面的任务没有的时候,CLR将会把其他有任务的线程中的未处理任务(比如上图中的WorkItem3),分配给这些空闲的线程(比如上图中的Thread3)去执行。这个机制也被称之为 偷窃机制。
这样做的其目的是每个线程都有事干,即提高线程池中的线程利用率。
为什么会出现Task:
本质问题:如何高效地对Thread进行编排?
本质理解:Task就是一个Thread的编排工具,它解决了任务之间如何串行、如何并行、如何嵌套、如何父子等关系的处理,让程序员可以重点关注任务,而不是Thread。
方式一:new Task,不推荐使用
// 无参数
var task = new Task(()=>
{
Console.WriteLine($"Current ThreadId={Environment.CurrentManagedThreadId}");
});
task.Start();
// 有参
var task = new Task((obj)=>
{
Console.WriteLine($"Current ThreadId={Environment.CurrentManagedThreadId}, Current Content={obj}");
}, "Hello World");
task.Start()
方式二:Task.Factory.StartNew
// 无参数
var task = Task.Factory.StartNew(()=>
{
Console.WriteLine($"Current ThreadId={Environment.CurrentManagedThreadId}");
});
// 有参
var task = Task.Factory.StartNew((obj)=>
{
Console.WriteLine($"Current ThreadId={Environment.CurrentManagedThreadId}, Current Content={obj}");
}, "Hello World");
方式三:Task.Run
// 无参数
var task = Task.Run(()=>
{
Console.WriteLine($"Current ThreadId={Environment.CurrentManagedThreadId}");
});
// 有参
var task = Task.Run((obj)=>
{
Console.WriteLine($"Current ThreadId={Environment.CurrentManagedThreadId}, Current Content={obj}");
}, "Hello World");
var task1 = Task.Factory.StartNew(()=>
{
new Sheet1().WriteSheet();
}).ContinueWith(t =>
{
new Sheet2().WriteSheet();
}).ContinueWith(t =>
{
new Sheet0().WriteSheet();
});
task1.Wait();
var sheets = new List<Sheet> { new Sheet1(), new Sheet2() };
var tasks = new Task[2];
for(int i=0; i<sheets.Count; i++)
{
tasks[i] = Task.Factory.StartNew((index)=>
{
sheets[(int)index].WriteSheet();
}, i);
}
Task.WhenAll(tasks).ContinueWith(t=>
{
new Sheet[0].WriteSheet();
}).Wait();
如果父Task中的任意一个子Task未完成,都不能继续。注意点:参数TaskCreationOptions.AttachedToParent
var sheets = new List<Sheet> { new Sheet1(), new Sheet2() };
//父task
var parent_task = Task.Factory.StartNew(() =>
{
//1. 子task1
var child_1_task = Task.Factory.StartNew(() =>
{
new Sheet1().WriteSheet();
}, TaskCreationOptions.AttachedToParent);
//2. 子task2
var child_2_task = Task.Factory.StartNew(() =>
{
new Sheet2().WriteSheet();
}, TaskCreationOptions.AttachedToParent);
});
var continueTask= parent_task.ContinueWith(t =>
{
new Sheet0().WriteSheet();
});
Task.WhenAll(continueTask);
最后等待可以有几种写法:
continueTask.Wait();
Task.WaitAll(continueTask);
Task.WaitAny(continueTask);
以上三种会阻塞主线程。而下面这种方式不会阻塞主线程。
Task.WhenAll(continueTask);
解析:WaitAll/WaitAny方法阻塞了当前线程直到全完。WhenAll方法会开启个新监控线程去判读括号里的所有线程执行情况并立即返回,等都完成了就退出监控线程并返回监控数据。
CTS = CancellationTokenSource,它主要是帮助开发者实现优雅退出(Graceful Exit)。
一是Thread.Abort()
二是增加临时变量如isStop来判断(hard cod)
namespace EDT.MultiThread.Demo
{
class Program
{
static void Main(string[] args)
{
CTSDemo();
}
static void CTSDemo()
{
var source = new CancellationTokenSource();
var task = Task.Factory.StartNew(() =>
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"当前线程:{Environment.CurrentManagedThreadId}, {DateTime.Now} 执行时间需要5s");
Thread.Sleep(1000);
}
}).ContinueWith(t =>
{
Console.WriteLine($"当前线程:{Environment.CurrentManagedThreadId}, 我是延续任务!");
}, source.Token);
Thread.Sleep(3000);
source.Cancel();
Console.WriteLine("主线程要取消你啦。。");
Console.ReadLine();
}
/// <summary>
/// 业务方法
/// </summary>
/// <param name="token"></param>
static void Run(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
Thread.Sleep(1000);
Console.WriteLine("1. 正在处理 redis 业务");
Thread.Sleep(1000);
Console.WriteLine("2. 正在处理 mongodb 业务");
Thread.Sleep(1000);
Console.WriteLine("3. 正在处理 sqlserver 业务");
Thread.Sleep(1000);
Console.WriteLine("4. 正在处理 mysql 业务");
}
}
}
}
TaskScheduler决定了将Task调度到什么地方去执行,即TaskScheduler决定了Task如何被调度。
自己实现一个单个Thread处理所有Task的TaskScheduler:
namespace EDT.MultiThread.Demo
{
public class CustomTaskScheduler : TaskScheduler
{
Thread thread = null;
BlockingCollection<Task> collection = new BlockingCollection<Task>();
public CustomTaskScheduler()
{
thread = new Thread(() =>
{
foreach (var task in collection.GetConsumingEnumerable())
{
TryExecuteTask(task);
}
});
thread.Start();
}
protected override IEnumerable<Task> GetScheduledTasks()
{
return collection.ToArray();
}
protected override void QueueTask(Task task)
{
collection.Add(task);
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
throw new NotImplementedException();
}
}
}
调用端示例代码:
var scheduler = new CustomTaskScheduler();
for (int i = 0; i < 100; i++)
{
var task = Task.Factory.StartNew(() =>
{
Console.WriteLine($"当前线程:{Environment.CurrentManagedThreadId}");
}, CancellationToken.None, TaskCreationOptions.None, scheduler);
}
Console.ReadLine();
本篇,我们复习了Thread与Task的基础知识
下一篇,我们复习面试常考的重点-异步(async/await)相关知识。