1 .NET多线程是什么?
1.1 进程与线程
进程是一种正在执行的程序。
线程是程序中的一个执行流。
多线程是指一个程序中可以同时运行多个不同的线程来执行不同的任务。
1.2 .NET中的线程
Thread
是创建和控制线程的类。
ManagedThreadId
是线程ID。
CurrentThread
是获取当前正在运行的线程。
1.3 同步与异步
同步是调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。(单线程)
异步调用一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。(多线程)
1.4 .NET中的多线程发展
主要有Thread,ThreadPool,Task
Thread
就是线程,需要自己调度,直接跟系统对接,相对管理比较复杂及效率差。
ThreadPool
是Thread
的一个升级版,ThreadPool
是从线程池中获取线程,如果线程池中又空闲的元素,则直接调用,如果没有才会创建,而Thread
则是会一直创建新的线程,要知道开启一个线程就算什么事都不做也会消耗大约1m的内存,是非常浪费性能的。但是ThreadPool
提供的接口比较少。
Task
和ThreadPool
是一样的,都是从线程池中取空闲的线程。比ThreadPool
调用接口更加丰富。目前.Net使用多线程管理,应该优先使用Task
。
代码:
/// <summary>
/// 多线程发展历史
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnHistory_Click(object sender, EventArgs e)
{
Console.WriteLine($"Thread start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
var threadStart = new ThreadStart(DoNothing);
var thread = new Thread(threadStart);
thread.Start();
Console.WriteLine($"Thread end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
Thread.Sleep(3000);
Console.WriteLine($"ThreadPool start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
var callback = new WaitCallback(DoNothing);
ThreadPool.QueueUserWorkItem(callback);
Console.WriteLine($"ThreadPool end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
Thread.Sleep(3000);
Console.WriteLine($"Task start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
Action action = DoNothing;
Task task = new Task(action);
task.Start();
Console.WriteLine($"Task end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
}
2 为什么需要多线程?
特点:
- 卡界面:单线程卡,多线程不卡
- 性能好:单线程差,多线程好(资源换性能)
- 执行顺序:单线程顺序,多线程无序
代码:
/// <summary>
/// 同步(单线程)
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSync_Click(object sender, EventArgs e)
{
Console.WriteLine($"btnSync_Click start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
for (int i = 0; i < 5; i++)
{
DoNothing();
}
Console.WriteLine($"btnSync_Click end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
}
/// <summary>
/// 异步(多线程)
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnAsync_Click(object sender, EventArgs e)
{
Console.WriteLine($"btnAsync_Click start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
for (int i = 0; i < 5; i++)
{
var ts = new ThreadStart(DoNothing);
var t = new Thread(ts);
t.Start();
}
Console.WriteLine($"btnAsync_Click end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
}
private void DoNothing()
{
Console.WriteLine($"DoNothing start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
Thread.Sleep(2000);
Console.WriteLine($"DoNothing end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
}
3 如何使用.NET多线程?
3.1 Task
3.1.1 创建任务
1、通过调用任务类构造函数实例化,但通过调用其Start()启动任务。
Task t1 = new Task(action, "alpha");
t1.Start();
2、通过调用TaskFactory.StartNew(Action < Object>,Object)方法在单个方法调用中实例化和启动任务。
Task t2 = Task.Factory.StartNew(action, "beta");
3、通过调用Run(Action)方法在单个方法调用中实例化和启动任务。
Task t3 = Task.Run(action);
3.1.2 从任务中返回值
Result
属性将阻止调用线程,直到任务完成。
Task<int> task1 = Task<int>.Factory.StartNew(() => 1);
int i = task1.Result;
3.1.3 等待任务完成
可以通过调用 Wait
方法来等待一个或多个任务完成,从而同步调用线程的执行以及它启动的异步任务。
调用无参数 Wait()
方法以无条件等待,直到任务完成。
调用Wait(Int32)
和 Wait(TimeSpan)
方法会阻止调用线程,直到任务完成或超时间隔(以先达到者为准)为止。
调用WaitAny(Task[])
方法等待一组任务中第一个任务完成。
调用WaitAll(Task[])
方法来等待一系列任务全部完成。
3.1.4 异常处理
调用代码可以通过使用 try/catch
块中的以下任意方法来处理异常:
await task
task.Wait()
task.Result
task.GetAwaiter().GetResult()
代码:
var task1 = Task.Run(() => { throw new Exception("This exception is expected!"); });
try
{
task1.Wait();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.ReadKey();
3.1.5 取消任务
你可以使用 CancellationTokenSource
类在以后某一时间发出取消请求。
static void Main(string[] args)
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var tasks = new ConcurrentBag<Task>();
Task tc;
for (int i = 0; i < 10; i++)
{
var k = i;
tc = Task.Run(() => DoNothing(k, token), token);
tasks.Add(tc);
}
char ch = Console.ReadKey().KeyChar;
if (ch == 'c' || ch == 'C')
{
tokenSource.Cancel();
Console.WriteLine("\n开始取消任务.");
}
try
{
Task.WhenAll(tasks.ToArray());
}
catch (OperationCanceledException)
{
Console.WriteLine($"\n{nameof(OperationCanceledException)} thrown\n");
}
finally
{
tokenSource.Dispose();
}
Console.ReadKey();
}
private static void DoNothing(int i, CancellationToken ct)
{
Console.WriteLine($"DoNothing start index:{i} id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
Thread.Sleep(i * 1000);
if (ct.IsCancellationRequested)
{
Console.WriteLine($"任务已取消 index:{i} id:{Thread.CurrentThread.ManagedThreadId} ");
ct.ThrowIfCancellationRequested();
}
Console.WriteLine($"DoNothing end index:{i} id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
}
3.1.6 实际案例
代码:
/// <summary>
/// Task实际案例
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnTask_Click(object sender, EventArgs e)
{
Console.WriteLine($"项目组接到任务 id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
Console.WriteLine($"项目经理设计数据库,设计原型,分配任务 id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
List<Task> tasks = new List<Task>();
tasks.Add(Task.Run(() => Coding("赵XX","前端页面")));
tasks.Add(Task.Run(() => Coding("王XX", "IOS页面")));
tasks.Add(Task.Run(() => Coding("黄XX", "后端接口")));
tasks.Add(Task.Run(() => Coding("杜XX", "后端接口")));
TaskFactory taskFactory = new TaskFactory();
taskFactory.ContinueWhenAll(tasks.ToArray(), t =>
{
Console.WriteLine($"项目经理发布,测试人员测试任务 id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
});
}
private void Coding(string personName,string taskName)
{
Console.WriteLine($"{personName}开发{taskName} id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
Thread.Sleep(2000);
}
3.2 async
和await
Async
和 Await
几乎与创建同步方法一样创建异步方法。
代码:
/// <summary>
/// Async和Await应用
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnAsyncAndAwait_Click(object sender, EventArgs e)
{
Console.WriteLine($"btnAsyncAndAwait_Click start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
DoNothingAsync();
Console.WriteLine($"btnAsyncAndAwait_Click end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
}
private async Task DoNothingAsync()
{
Console.WriteLine($"DoNothingAsync start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
await Task.Run(() => {
Console.WriteLine($"DoNothingAsync Task start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
Thread.Sleep(2000);
Console.WriteLine($"DoNothingAsync Task end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
});
Console.WriteLine($"DoNothingAsync end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
}