0、目录
.NET
中有多种令牌机制,用于实现不同的同步、取消和通信功能。常见的令牌机制包括:
CancellationToken
: 主要用于取消任务。Semaphore
和SemaphoreSlim
: 用于控制并发访问的信号量。Mutex
: 用于线程或进程间互斥访问。Monitor
: 用于线程间同步的锁机制。AutoResetEvent
和ManualResetEvent
: 用于线程之间的等待和通知。TaskCompletionSource
: 用于手动控制异步任务的完成。AccessToken
: 用于身份验证和授权的令牌。
每种令牌机制都有其特定的应用场景和功能,可以根据需求选择适合的方式来管理并发、同步和任务控制。
1、具体说明
1.1 CancellationToken
CancellationTokenSource
是 .NET
中用于处理取消操作的一个类,通常与 CancellationToken
一起使用来实现异步操作或长时间运行任务的取消机制。说明:
CancellationToken
: 这是一个值类型,表示一个可以传递给异步任务、线程或操作的“取消令牌”。通过 CancellationToken,任务能够检测到是否请求了取消操作。CancellationTokenSource
: 这是用来创建 CancellationToken 的类,控制取消的来源。当希望取消某个任务时,CancellationTokenSource 负责发出取消信号。
简单来说,CancellationTokenSource
充当了“控制者”的角色,而 CancellationToken
作为“信号”的传递者。
附上案例作以说明:
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
// 创建一个 CancellationTokenSource 实例
var cts = new CancellationTokenSource();
// 获取 CancellationToken
CancellationToken token = cts.Token;
// 启动一个异步任务并传入 CancellationToken
var task = LongRunningTask(token);
// 模拟一段时间后请求取消
await Task.Delay(2000);
Console.WriteLine("Cancelling the task...");
cts.Cancel(); // 它会向所有使用该令牌的任务发出取消信号
// 等待任务完成
await task;
}
static async Task LongRunningTask(CancellationToken token)
{
for (int i = 0; i < 10; i++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("Task was cancelled.");
return;
}
Console.WriteLine($"Task running: {i + 1}");
await Task.Delay(1000); // 模拟一个耗时操作
}
}
}
CancellationTokenSource
提供了几种方式来控制取消操作的触发:
- 调用 Cancel(): 手动触发取消,通常是在某个条件满足时(如超时、用户请求等)。
- 超时: 可以为 CancellationTokenSource 设置一个超时,当超时到达时,它会自动发出取消信号。
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); // CancellationTokenSource 会在 5 秒后自动触发取消
- 链式取消: 可以将多个
CancellationTokenSource
联合在一起。当其中一个源发出取消请求时,所有相关的任务都会被取消。
var cts1 = new CancellationTokenSource();
var cts2 = new CancellationTokenSource();
var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token);
1.2 SemaphoreSlim
和 Semaphore
这两个类是 .NET
中用于 控制并发访问 的令牌机制。它们用于限制同时访问某个资源的线程数量,通常用于多线程环境中对共享资源的访问进行同步。
SemaphoreSlim
: 是一个轻量级的信号量类,适用于轻量级的线程同步操作。它的主要功能是限制对资源的并发访问,通常比Semaphore
性能更好,适用于高频次的短时间访问控制。案例代码如下:
var semaphore = new SemaphoreSlim(2); // 限制最多 2 个线程并发访问
async Task AccessResource()
{
await semaphore.WaitAsync(); // 获取信号量,等待可用的令牌
try
{
Console.WriteLine("Resource accessed");
await Task.Delay(1000); // 模拟访问资源
}
finally
{
semaphore.Release(); // 释放信号量
}
}
Semaphore
: 也是一种信号量,但是它相对来说更重量级,适用于需要在多个进程之间同步的场景(例如跨进程控制共享资源)。案例代码如下:
var semaphore = new Semaphore(2, 5); // 最小 2 个线程可以访问,最多 5 个线程可以并发
semaphore.WaitOne(); // 获取信号量
try
{
Console.WriteLine("Resource accessed");
}
finally
{
semaphore.Release(); // 释放信号量
}
1.3 Mutex
(互斥锁)
Mutex
是另一种令牌机制,它用于在多个线程(或进程)之间进行同步,确保在同一时刻只有一个线程可以访问某个资源。与信号量不同,Mutex 主要用于线程间的互斥访问,而不适用于控制并发数量。它可以在进程间使用,因为它是操作系统级别的同步对象。
Mutex
适用于跨线程、跨进程的资源同步。如果线程无法获取 Mutex
,它会阻塞等待。
var mutex = new Mutex();
try
{
mutex.WaitOne(); // 获取互斥锁
Console.WriteLine("Accessing critical section");
// 执行需要互斥访问的操作
}
finally
{
mutex.ReleaseMutex(); // 释放互斥锁
}
1.4 Monitor
Monitor
是 .NET
提供的一种低级同步机制,它为代码块提供互斥锁。它通常与 lock
语句一起使用,是对 Mutex
更轻量的同步方式。Monitor
用于同步线程对共享资源的访问,保证在某个时刻只有一个线程可以进入临界区。
Monitor 主要用于线程间的同步,不适用于跨进程的场景。
private static readonly object lockObject = new object();
void AccessSharedResource()
{
lock (lockObject) // 这里的 lock 等同于 Monitor.Enter/Monitor.Exit
{
Console.WriteLine("Accessing shared resource");
// 执行需要互斥访问的操作
}
}
1.5 AutoResetEvent
和 ManualResetEvent
这两个类是用于线程间通信的同步原语。它们通过信号量机制实现线程的等待和通知机制,通常用于线程之间的协调。
1.5.1 AutoResetEvent
AutoResetEvent
是一种自动重置的事件,其基于“队列”的原理工作的。当多个线程调用 WaitOne()
等待 AutoResetEvent
时,它们会按顺序排队等待信号。
当调用 Set()
方法时,它会将事件的状态从无信号(false)切换为信号(true),并立即释放一个等待的线程,释放的是第一个在等待队列中的线程,需注意,此处的释放并不意味着将线程“关闭”或“销毁”,其只是指将线程从等待状态中唤醒,让它继续执行。
此时,释放一个线程后, AutoResetEvent
会自动将状态重置为 false
,其他线程仍然会处于阻塞状态,直到下次调用 Set()
。
总结:
- 每次调用
Set()
会唤醒一个等待中的线程,且AutoResetEvent
会自动将状态重置为false
。 - 唤醒的线程会继续执行,直到它遇到下一个
WaitOne()
时才会再次受AutoResetEvent
的影响。 - 唤醒的线程不会因为之前的
Set()
而被干扰,除非它显式地再次调用WaitOne()
等待。
var autoResetEvent = new AutoResetEvent(false); // 初始状态为无信号
// 线程1:发送信号
autoResetEvent.Set();
// 线程2:等待信号
autoResetEvent.WaitOne(); // 会阻塞,直到收到信号
1.5.2 ManualResetEvent
ManualResetEvent
有一个 信号状态,它可以是 true
或 false
。如果状态为 true
,那么所有等待的线程都会被唤醒并继续执行;如果状态为 false
,那么所有调用 WaitOne() 的线程都会被阻塞,直到状态变为 true。
与AutoResetEvent
相比,ManualResetEvent
是一种手动重置的事件,它不会自动重置。调用 Set
后,所有等待的线程都会被通知并继续执行,直到手动调用 Reset
来将状态重置为无信号。
关键点:
-
Set()
方法:当调用Set()
时,ManualResetEvent
的状态会被设置为true
。此时,所有 在WaitOne()
上等待的线程都会被唤醒并继续执行。(调用WaitOne()
会使线程进入等待队列)
这个唤醒过程是批量的,与AutoResetEvent
不同,后者每次只释放一个等待的线程。ManualResetEvent
会释放所有等待的线程。 -
Reset()
方法:当调用Reset()
时,ManualResetEvent
会将状态设置回false
,使得所有后续调用WaitOne()
的线程都会被阻塞,直到下次调用Set()
。 -
状态持久性:与
AutoResetEvent
不同,ManualResetEvent
的状态在被设置为true
后,会持续保持为true
,直到显式调用Reset()
将其重置为false
。因此,ManualResetEvent
可以让多个线程同时继续执行,直到手动调用Reset()
来阻塞它们。
总结:
-
调用
Set()
:状态从false
变为true
,所有等待的线程都会被唤醒,并继续执行各自的任务。此时,ManualResetEvent
保持为true
,并且所有后续的WaitOne()
调用将不会阻塞,直到状态被重置。 -
调用
Reset()
:将状态从true
设置为false
,此时 所有新的WaitOne()
调用 会被阻塞,直到下一次调用Set()
。
案例代码如下:
ManualResetEvent mre = new ManualResetEvent(false); // 初始状态为 false
Thread t1 = new Thread(() =>
{
Console.WriteLine("Thread 1 waiting...");
mre.WaitOne(); // 线程 1 会阻塞,直到状态为 true
Console.WriteLine("Thread 1 started.");
});
Thread t2 = new Thread(() =>
{
Console.WriteLine("Thread 2 waiting...");
mre.WaitOne(); // 线程 2 会阻塞,直到状态为 true
Console.WriteLine("Thread 2 started.");
});
t1.Start();
t2.Start();
Thread.Sleep(1000); // 模拟一些延时
Console.WriteLine("Main thread setting event.");
mre.Set(); // 触发 Set(),将状态设为 true,唤醒所有等待的线程
// 输出:
// Thread 1 waiting...
// Thread 2 waiting...
// Main thread setting event.
// Thread 1 started.
// Thread 2 started.
1.6 TaskCompletionSource
虽然 TaskCompletionSource
本身不算一个典型的令牌,但它允许创建自定义的异步任务控制机制。它是一个用于手动控制异步任务完成状态的对象,通常与异步编程结合使用。它提供了一个 Task
,可以通过设置任务的完成状态来控制任务何时完成。
TaskCompletionSource
通常用于自定义的异步操作中,可以在操作完成时标记任务为已完成、已取消或已失败。
var tcs = new TaskCompletionSource<bool>();
// 模拟一些操作
Task.Run(() =>
{
// 进行一些耗时操作
Thread.Sleep(2000);
tcs.SetResult(true); // 操作完成
});
// 等待结果
bool result = await tcs.Task;
Console.WriteLine($"Operation result: {result}");
1.7 AccessToken
(授权令牌)
在一些需要身份验证和授权的应用中,AccessToken
是一种用于控制用户访问权限的令牌。它通常用于基于 OAuth 或其他身份验证协议的应用程序中,确保用户能够访问特定的资源。
AccessToken 是用来标识和验证用户身份的令牌,通常由身份提供者(如 OAuth2.0 提供者)颁发,并且可以在 API 请求中携带。
var accessToken = "your-access-token";
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await client.GetAsync("https://api.example.com/data");
var data = await response.Content.ReadAsStringAsync();