WaitHandle类
用于 等待信号。可以 等待 不同的信号。
WaitHandle:
- 是抽象基类
SafeWaitHandle属性:可以将一个句柄赋予一个操作系统资源,并等待该句柄。- e.g. 可以指定一个
SafeWaitHandle等待文件I/O操作的完成
- e.g. 可以指定一个
- 可以从中派生出一些类:
MutexSemaphoreEventWaitHandle
等待 一个信号 的出现: 静态方法
WaitOne()
等待 必须发出信号的 多个对象: 静态方法
WaitAll()
等待 多个对象 中的一个: 静态方法
WaitAny()
Mutex类
- Mutex (mutual exclusion, 互斥)
跨多个进程 的同步访问
只有一个线程 能够拥有 互斥锁定, 访问 受互斥保护的 代码
Mutex的构造函数:
- 第一个参数: 指定
Mutex最初 是否应有主调线程拥有 - 第二个参数: 指定
Mutex的名称 - 第三个参数: 得到
Mutex是否是新定义的false, 表示已经定义了- 而且可以是在另一个进程中定义的。操作系统 能够按名称 识别
Mutex - 如果
Mutex没有名称,就不能在不同的进行间共享
- 而且可以是在另一个进程中定义的。操作系统 能够按名称 识别
bool createdNew;
var mutex = new Mutex(false, "CSharpMutex", out createNew);
打开已有的Mutex:
Mutex.OpenExisting()- 不需要 用Mutex构造函数创建Mutex时的
.NET权限
- 不需要 用Mutex构造函数创建Mutex时的
使用Mutex:
- 用
WaitOne()获得 互斥锁定 - 用
ReleaseMutex(),可以释放 互斥锁定
if(mutex.WaitOne())
{
try
{
//受互斥保护的 同步代码
}
finally
{
mutex.ReleaseMutex();
}
}
else
{
//等待过程中 如果有问题发生 在这里处理
}
使用Mutex:禁止 应用程序 启动两次
系统 能识别 有名称的
Mutex
class Program
{
static void Main()
{
bool mutexCreated;
var mutex = new Mutex(false, "SingletonAppMutex", out mutexCreated);
if (!mutexCreated)
{
Console.WriteLine("You can only start one instance of the application.");
Console.WriteLine("Exiting.");
return;
}
Console.WriteLine("Application running");
Console.WriteLine("Press return to exit");
Console.ReadLine();
}
}
信号量
信号量 是一种计数的 互斥锁定。
信号量 类似于互斥,但区别是,信号量 可以同时 由多个线程使用
如果 需要限制 可以访问可用资源的 线程数,信号量 就很有用 例如, 系统有3个物理端口可用,只允许3个线程同时访问I/O端口,第4个线程需要等待前3个线程中的一个释放资源
.NET Core 为 信号量 功能 提供了两个类:
Semaphore和SemaphoreSlim
Semaphore类
- 可以给定名称
- 使用系统范围内的资源
- 允许在不同进程间进行同步
SemaphoreSlim类
相比于
Semaphore,SemaphoreSlim是 对较短等待时间 进行了优化 的轻量版
SemaphoreSlim的构造函数:
- 第一个参数:最初释放的锁定数
- 第二个参数: 锁定个数的计数
- 第一个参数 和 第二个参数 的差值:已分配线程 的计数值
给定了名称 就可以在进程间共享。如果不给定名称,就只能在这个进程中使用。
下面的例子:
- 信号量的计数为3, 所以有3个任务可以获得锁定。
- 第4个任务必须等待,这里还定义了最长等待时间,为600毫秒。
- 调用了
SemaphoreSlim的Wait(毫秒数)方法
- 调用了
- 解除锁定时,务必要在任何情况下解除锁定。重要!
- 调用了
SemaphoreSlim的Release()方法
- 调用了
class Program
{
static void Main()
{
int taskCount = 6;
int semaphoreCount = 3;
var semaphore = new SemaphoreSlim(semaphoreCount, semaphoreCount);
var tasks = new Task[taskCount];
for (int i = 0; i < taskCount; i++)
{
tasks[i] = Task.Run(() => TaskMain(semaphore));
}
Task.WaitAll(tasks);
Console.WriteLine("All tasks finished");
}
private static void TaskMain(SemaphoreSlim semaphore)
{
bool isCompleted = false;
while (!isCompleted)
{
if (semaphore.Wait(600))
{
try
{
Console.WriteLine($"Task {Task.CurrentId} locks the semaphore");
Task.Delay(2000).Wait();
}
finally
{
Console.WriteLine($"Task {Task.CurrentId} releases the semaphore");
semaphore.Release();
isCompleted = true;
}
}
else
{
Console.WriteLine($"Timeout for task {Task.CurrentId}; wait again");
}
}
}
}
输出:
Task 1 locks the semaphore
Task 3 locks the semaphore
Task 5 locks the semaphore
Timeout for task 2; wait again
Timeout for task 4; wait again
Timeout for task 6; wait again
Timeout for task 2; wait again
Timeout for task 4; wait again
Timeout for task 6; wait again
Timeout for task 2; wait again
Timeout for task 4; wait again
Timeout for task 6; wait again
Task 5 releases the semaphore
Task 1 releases the semaphore
Task 4 locks the semaphore
Task 2 locks the semaphore
Task 3 releases the semaphore
Task 6 locks the semaphore
Task 4 releases the semaphore
Task 2 releases the semaphore
Task 6 releases the semaphore
All tasks finished
Events类
与互斥
Mutex,信号量Semaphore一样,事件Event也是一种系统范围内的资源同步方法
.NET Framework 在 System.Threading命名空间中提供了以下Event类:
ManualResetEventAutoResetEventManualResetEventSlimCountdownEvent
可以使用
Event通知 其他任务:这里有一些数据,并完成了一些操作。
Event可以发信号,也可以不发信号
使用
WaitHandle类,任务 可以等待处于发信号状态的Event
- e.g
ManualResetEventSlim有属性WaitHandle
ManualResetEventSlim
- 调用
Set()方法,ManualResetEventSlim发信号 - 调用
Reset()方法,ManualResetEventSlim变为 不发信号 的状态 - 如果多个线程等待一个
ManualResetEventSlim发信号,并调用了Set()方法,那么就释放所有等待线程 - 如果一个线程刚刚调用了
WaitOne(), 但ManualResetEventSlim已经发出信号,等待的线程就可以继续等待
AutoResetEvent
- 调用
Set()方法,AutoResetEvent发信号 - 调用
Reset()方法,AutoResetEvent变为 不发信号 的状态 - 如果一个线程在等待
AutoResetEvent发信号,当这个线程的等待状态结束时,AutoResetEvent会自动变为不发信号的状态 - 如果多个线程在等待
AutoResetEvent发信号,就只会有一个线程结束其等待状态。这个线程,它是优先级最高的线程
示例 ManualResetEventSlim
只要完成了计算,就调用ManualResetEventSlim类的Set()方法 发信号
public class Calculator
{
private ManualResetEventSlim _mEvent;
public int Result { get; private set; }
public Calculator(ManualResetEventSlim ev)
{
_mEvent = ev;
}
public void Calculation(int x, int y)
{
Console.WriteLine($"Task {Task.CurrentId} starts calculation");
Task.Delay(new Random().Next(3000)).Wait();
Result = x + y;
// signal the event-completed!
Console.WriteLine($"Task {Task.CurrentId} is ready");
_mEvent.Set();
}
}
WaitHandle.WaitAny()WaitHandle.Timeout
static void Main()
{
const int taskCount = 4;
var mEvents = new ManualResetEventSlim[taskCount];
var waitHandles = new WaitHandle[taskCount];
var calcs = new Calculator[taskCount];
for (int i = 0; i < taskCount; i++)
{
int i1 = i;
mEvents[i] = new ManualResetEventSlim(false);
waitHandles[i] = mEvents[i].WaitHandle;
calcs[i] = new Calculator(mEvents[i]);
Task.Run(() => calcs[i1].Calculation(i1 + 1, i1 + 3));
}
for (int i = 0; i < taskCount; i++)
{
// int index = WaitHandle.WaitAny(mEvents.Select(e => e.WaitHandle).ToArray());
int index = WaitHandle.WaitAny(waitHandles);
if (index == WaitHandle.WaitTimeout)
{
Console.WriteLine("Timeout!!");
}
else
{
mEvents[index].Reset();
Console.WriteLine($"finished task for {index}, result: {calcs[index].Result}");
}
}
}
输出:
Task 1 starts calculation
Task 3 starts calculation
Task 4 starts calculation
Task 2 starts calculation
Task 3 is ready
finished task for 2, result: 8
Task 1 is ready
finished task for 0, result: 4
Task 2 is ready
finished task for 1, result: 6
Task 4 is ready
finished task for 3, result: 10
示例 CountdownEvent
把工作分到多个
Task中,并在之后 合并结果
- 不需要为每一个
Task创建单独的Event - 只需要创建一个
Event CountdownEvent为所有设置了Event的Task定义一个初始数字- 在达到该数字后,
CountdownEvent就发信号
CountdownEvent有:
Signal()方法: 发信号Wait()方法:等待直到达到计数值
class Program
{
static void Main()
{
const int taskCount = 4;
var cEvent = new CountdownEvent(taskCount);
var calcs = new Calculator[taskCount];
for (int i = 0; i < taskCount; i++)
{
calcs[i] = new Calculator(cEvent);
int i1 = i;
Task.Run(() => calcs[i1].Calculation(i1 + 1, i1 + 3));
}
cEvent.Wait();
Console.WriteLine("all finished");
for (int i = 0; i < taskCount; i++)
{
Console.WriteLine($"task for {i}, result: {calcs[i].Result}");
}
}
}
public class Calculator
{
private CountdownEvent _cEvent;
public int Result { get; private set; }
public Calculator(CountdownEvent ev)
{
_cEvent = ev;
}
public void Calculation(int x, int y)
{
Console.WriteLine($"Task {Task.CurrentId} starts calculation");
Task.Delay(new Random().Next(3000)).Wait();
Result = x + y;
// signal the event-completed!
Console.WriteLine($"Task {Task.CurrentId} is ready");
_cEvent.Signal();
}
}
输出:
Task 2 starts calculation
Task 1 starts calculation
Task 4 starts calculation
Task 3 starts calculation
Task 3 is ready
Task 2 is ready
Task 1 is ready
Task 4 is ready
all finished
task for 0, result: 4
task for 1, result: 6
task for 2, result: 8
task for 3, result: 10
Barrier 类
把工作分到多个
Task中,并在之后 合并结果
用于 需要 同步的 参与者。激活一个
Task时,动态地添加其他参与者
- 例如:
- 从 父
Task中创建 子Task - 参与者 在继续之前,等待所有 其他参与者 完成工作
- 从 父
示例:
填充随机数据:
public static IEnumerable<string> FillData(int size)
{
var r = new Random();
return Enumerable.Range(0, size).Select(x => GetString(r));
}
private static string GetString(Random r)
{
var sb = new StringBuilder(6);
for (int i = 0; i < 6; i++)
{
sb.Append((char)(r.Next(26) + 97));
}
return sb.ToString();
}
private static void LogBarrierInformation(string info, Barrier barrier)
{
Console.WriteLine($"Task {Task.CurrentId}: {info}. {barrier.ParticipantCount} current and {barrier.ParticipantsRemaining} remaining participants, phase {barrier.CurrentPhaseNumber}");
}
- barrier.
SignalAndWait(); - barrier.
RemoveParticipant();
private static void CalculationInTask(int jobNumber, int partitionSize, Barrier barrier, IList<string>[] coll, int loops, int[][] results)
{
LogBarrierInformation("CalculationInTask started", barrier);
for (int i = 0; i < loops; i++)
{
var data = new List<string>(coll[i]);
int start = jobNumber * partitionSize;
int end = start + partitionSize;
Console.WriteLine($"Task {Task.CurrentId} in loop {i}: partition from {start} to {end}");
for (int j = start; j < end; j++)
{
char c = data[j][0];
results[i][c - 97]++;
}
Console.WriteLine($"Calculation completed from task {Task.CurrentId} in loop {i}. " +
$"{results[i][0]} times a, {results[i][25]} times z");
LogBarrierInformation("sending signal and wait for all", barrier);
barrier.SignalAndWait();
LogBarrierInformation("waiting completed", barrier);
}
barrier.RemoveParticipant();
LogBarrierInformation("finished task, removed participant", barrier);
}
- barrier.
AddParticipant();
static void Main()
{
const int numberTasks = 2;
const int partitionSize = 1000000;
const int loops = 5;
var taskResults = new Dictionary<int, int[][]>();
var data = new List<string>[loops];
for (int i = 0; i < loops; i++)
{
data[i] = new List<string>(FillData(partitionSize * numberTasks));
}
var barrier = new Barrier(1);
LogBarrierInformation("initial participants in barrier", barrier);
for (int i = 0; i < numberTasks; i++)
{
barrier.AddParticipant();
int jobNumber = i;
taskResults.Add(i, new int[loops][]);
for (int loop = 0; loop < loops; loop++)
{
taskResults[i][loop] = new int[26];
}
Console.WriteLine($"Main - starting task job {jobNumber}");
Task.Run(() => CalculationInTask(jobNumber, partitionSize, barrier, data, loops, taskResults[jobNumber]));
}
for (int loop = 0; loop < 5; loop++)
{
LogBarrierInformation("main task, start signaling and wait", barrier);
barrier.SignalAndWait();
LogBarrierInformation("main task waiting completed", barrier);
// var resultCollection = tasks[0].Result.Zip(tasks[1].Result, (c1, c2) => c1 + c2);
int[][] resultCollection1 = taskResults[0];
int[][] resultCollection2 = taskResults[1];
var resultCollection = resultCollection1[loop].Zip(resultCollection2[loop], (c1, c2) => c1 + c2);
char ch = 'a';
int sum = 0;
foreach (var x in resultCollection)
{
Console.WriteLine($"{ch++}, count: {x}");
sum += x;
}
LogBarrierInformation($"main task finished loop {loop}, sum: {sum}", barrier);
}
Console.WriteLine("at the end");
Console.ReadLine();
}
输出:
Task : initial participants in barrier. 1 current and 1 remaining participants, phase 0
Main - starting task job 0
Main - starting task job 1
Task : main task, start signaling and wait. 3 current and 3 remaining participants, phase 0
Task 2: CalculationInTask started. 3 current and 2 remaining participants, phase 0
Task 1: CalculationInTask started. 3 current and 2 remaining participants, phase 0
Task 1 in loop 0: partition from 0 to 1000000
Task 2 in loop 0: partition from 1000000 to 2000000
Calculation completed from task 2 in loop 0. 38668 times a, 38413 times z
Task 2: sending signal and wait for all. 3 current and 2 remaining participants, phase 0
Calculation completed from task 1 in loop 0. 38660 times a, 38744 times z
Task 1: sending signal and wait for all. 3 current and 1 remaining participants, phase 0
Task 1: waiting completed. 3 current and 3 remaining participants, phase 1
Task : main task waiting completed. 3 current and 3 remaining participants, phase 1
Task 2: waiting completed. 3 current and 3 remaining participants, phase 1
Task 1 in loop 1: partition from 0 to 1000000
a, count: 77328
b, count: 76839
c, count: 76765
d, count: 77133
e, count: 77442
f, count: 76912
g, count: 77098
h, count: 77284
i, count: 76786
j, count: 76388
k, count: 76534
l, count: 76792
m, count: 76875
n, count: 76834
o, count: 77199
p, count: 76708
q, count: 76812
r, count: 76618
s, count: 76567
t, count: 76915
u, count: 76894
v, count: 77524
w, count: 76721
x, count: 76895
y, count: 76980
z, count: 77157
Task 2 in loop 1: partition from 1000000 to 2000000
Calculation completed from task 1 in loop 1. 38600 times a, 38544 times z
Task 1: sending signal and wait for all. 3 current and 3 remaining participants, phase 1
Task : main task finished loop 0, sum: 2000000. 3 current and 3 remaining participants, phase 1
Task : main task, start signaling and wait. 3 current and 2 remaining participants, phase 1
Calculation completed from task 2 in loop 1. 38403 times a, 38234 times z
Task 2: sending signal and wait for all. 3 current and 1 remaining participants, phase 1
Task 2: waiting completed. 3 current and 3 remaining participants, phase 2
Task 1: waiting completed. 3 current and 3 remaining participants, phase 2
Task : main task waiting completed. 3 current and 3 remaining participants, phase 2
a, count: 77003
Task 2 in loop 2: partition from 1000000 to 2000000
b, count: 76881
c, count: 76867
Task 1 in loop 2: partition from 0 to 1000000
d, count: 77250
Calculation completed from task 2 in loop 2. 38537 times a, 38588 times z
e, count: 76682
Task 2: sending signal and wait for all. 3 current and 3 remaining participants, phase 2
f, count: 76637
g, count: 77049
Calculation completed from task 1 in loop 2. 38626 times a, 38322 times z
Task 1: sending signal and wait for all. 3 current and 2 remaining participants, phase 2
h, count: 77197
i, count: 76543
j, count: 77088
k, count: 77112
l, count: 76660
m, count: 76578
n, count: 76928
o, count: 77122
p, count: 76920
q, count: 77261
r, count: 77041
s, count: 77079
t, count: 77000
u, count: 76506
v, count: 76808
w, count: 77053
x, count: 76723
y, count: 77234
z, count: 76778
Task : main task finished loop 1, sum: 2000000. 3 current and 1 remaining participants, phase 2
Task : main task, start signaling and wait. 3 current and 1 remaining participants, phase 2
Task : main task waiting completed. 3 current and 3 remaining participants, phase 3
a, count: 77163
b, count: 77140
c, count: 77058
Task 2: waiting completed. 3 current and 3 remaining participants, phase 3
Task 1: waiting completed. 3 current and 3 remaining participants, phase 3
d, count: 77249
e, count: 77011
f, count: 76396
g, count: 76514
h, count: 77153
i, count: 76942
j, count: 76500
k, count: 76943
l, count: 76767
m, count: 77170
n, count: 76925
o, count: 76379
p, count: 77006
q, count: 76795
r, count: 76934
s, count: 76794
t, count: 76819
u, count: 77061
v, count: 77194
w, count: 76988
x, count: 77030
y, count: 77159
z, count: 76910
Task : main task finished loop 2, sum: 2000000. 3 current and 3 remaining participants, phase 3
Task : main task, start signaling and wait. 3 current and 3 remaining participants, phase 3
Task 1 in loop 3: partition from 0 to 1000000
Task 2 in loop 3: partition from 1000000 to 2000000
Calculation completed from task 1 in loop 3. 38630 times a, 38816 times z
Task 1: sending signal and wait for all. 3 current and 2 remaining participants, phase 3
Calculation completed from task 2 in loop 3. 38764 times a, 38564 times z
Task 2: sending signal and wait for all. 3 current and 1 remaining participants, phase 3
Task 2: waiting completed. 3 current and 3 remaining participants, phase 4
Task : main task waiting completed. 3 current and 3 remaining participants, phase 4
Task 1: waiting completed. 3 current and 3 remaining participants, phase 4
a, count: 77394
b, count: 76598
c, count: 76820
d, count: 76897
e, count: 76764
f, count: 76677
g, count: 77224
h, count: 76891
i, count: 76460
j, count: 76731
k, count: 76856
l, count: 76930
m, count: 76530
n, count: 77159
o, count: 76794
Task 1 in loop 4: partition from 0 to 1000000
Task 2 in loop 4: partition from 1000000 to 2000000
p, count: 76908
q, count: 76981
r, count: 77120
s, count: 76670
t, count: 76929
u, count: 76991
v, count: 76722
w, count: 77093
x, count: 77204
y, count: 77277
z, count: 77380
Task : main task finished loop 3, sum: 2000000. 3 current and 3 remaining participants, phase 4
Task : main task, start signaling and wait. 3 current and 3 remaining participants, phase 4
Calculation completed from task 2 in loop 4. 38388 times a, 38668 times z
Task 2: sending signal and wait for all. 3 current and 2 remaining participants, phase 4
Calculation completed from task 1 in loop 4. 38530 times a, 38537 times z
Task 1: sending signal and wait for all. 3 current and 1 remaining participants, phase 4
Task 1: waiting completed. 3 current and 3 remaining participants, phase 5
Task 1: finished task, removed participant. 2 current and 2 remaining participants, phase 5
Task 2: waiting completed. 3 current and 3 remaining participants, phase 5
Task 2: finished task, removed participant. 1 current and 1 remaining participants, phase 5
Task : main task waiting completed. 3 current and 3 remaining participants, phase 5
a, count: 76918
b, count: 76912
c, count: 76938
d, count: 77079
e, count: 76960
f, count: 76880
g, count: 77161
h, count: 76703
i, count: 77089
j, count: 77110
k, count: 76801
l, count: 77049
m, count: 77120
n, count: 76800
o, count: 77029
p, count: 76692
q, count: 76530
r, count: 76789
s, count: 76644
t, count: 76994
u, count: 77307
v, count: 76851
w, count: 77206
x, count: 76660
y, count: 76573
z, count: 77205
Task : main task finished loop 4, sum: 2000000. 1 current and 1 remaining participants, phase 5
at the end
ReaderWriterLockSlim 类
提供锁定功能。如果 没有写入器锁定资源,就允许 多个读取器 访问资源。如果 有写入器锁定资源,那么 只能有一个写入器 锁定资源。
有 阻塞 或 不阻塞 的方法 来获取锁定
- 阻塞:
EnterReadLock()EnterWriteLock()
- 不阻塞:
TryEnterReadLock()TryEnterWriteLock()
如果 先读取资源,后写入资源, 可以用以下方法 实现: 获得 写入锁定,而不需要释放 读取锁定
EnterUpgradableReadLock()TryEnterUpgradableReadLock()
示例
- _rwl.
EnterReadLock() - _rwl.
ExitReadLock() - _rwl.
TryEnterWriteLock(50) - _rwl.
ExitWriteLock();
class Program
{
private static List<int> _items = new List<int>() { 0, 1, 2, 3, 4, 5 };
private static ReaderWriterLockSlim _rwl =
new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
public static void ReaderMethod(object reader)
{
try
{
_rwl.EnterReadLock();
for (int i = 0; i < _items.Count; i++)
{
Console.WriteLine($"reader {reader}, loop: {i}, item: {_items[i]}");
Task.Delay(40).Wait();
}
}
finally
{
_rwl.ExitReadLock();
}
}
public static void WriterMethod(object writer)
{
try
{
while (!_rwl.TryEnterWriteLock(50))
{
Console.WriteLine($"Writer {writer} waiting for the write lock");
Console.WriteLine($"current reader count: {_rwl.CurrentReadCount}");
}
Console.WriteLine($"Writer {writer} acquired the lock");
for (int i = 0; i < _items.Count; i++)
{
_items[i]++;
Task.Delay(50).Wait();
}
Console.WriteLine($"Writer {writer} finished");
}
finally
{
_rwl.ExitWriteLock();
}
}
static void Main()
{
var taskFactory = new TaskFactory(TaskCreationOptions.LongRunning,
TaskContinuationOptions.None);
var tasks = new Task[6];
tasks[0] = taskFactory.StartNew(WriterMethod, 1);
tasks[1] = taskFactory.StartNew(ReaderMethod, 1);
tasks[2] = taskFactory.StartNew(ReaderMethod, 2);
tasks[3] = taskFactory.StartNew(WriterMethod, 2);
tasks[4] = taskFactory.StartNew(ReaderMethod, 3);
tasks[5] = taskFactory.StartNew(ReaderMethod, 4);
Task.WaitAll(tasks);
}
}
输出:
- 读取器先获得了锁定(谁先,这不是一定的),所有读取器一起工作,两个写入器都等待
- 第一个写入器获得了锁定,第二个写入器等待
- 第二个写入器获得了锁定
reader 2, loop: 0, item: 0
reader 1, loop: 0, item: 0
reader 3, loop: 0, item: 0
reader 4, loop: 0, item: 0
Writer 2 waiting for the write lock
Writer 1 waiting for the write lock
current reader count: 4
current reader count: 4
reader 2, loop: 1, item: 1
reader 1, loop: 1, item: 1
reader 4, loop: 1, item: 1
reader 3, loop: 1, item: 1
Writer 2 waiting for the write lock
current reader count: 4
Writer 1 waiting for the write lock
reader 2, loop: 2, item: 2
current reader count: 4
reader 4, loop: 2, item: 2
reader 1, loop: 2, item: 2
reader 3, loop: 2, item: 2
reader 2, loop: 3, item: 3
Writer 1 waiting for the write lock
current reader count: 4
Writer 2 waiting for the write lock
current reader count: 4
reader 4, loop: 3, item: 3
reader 3, loop: 3, item: 3
reader 1, loop: 3, item: 3
reader 2, loop: 4, item: 4
reader 4, loop: 4, item: 4
reader 3, loop: 4, item: 4
Writer 1 waiting for the write lock
current reader count: 4
reader 1, loop: 4, item: 4
Writer 2 waiting for the write lock
reader 2, loop: 5, item: 5
current reader count: 4
reader 3, loop: 5, item: 5
reader 4, loop: 5, item: 5
Writer 1 waiting for the write lock
reader 1, loop: 5, item: 5
current reader count: 3
Writer 2 waiting for the write lock
current reader count: 1
Writer 1 acquired the lock
Writer 2 waiting for the write lock
current reader count: 0
Writer 2 waiting for the write lock
current reader count: 0
Writer 2 waiting for the write lock
current reader count: 0
Writer 2 waiting for the write lock
current reader count: 0
Writer 2 waiting for the write lock
current reader count: 0
Writer 2 waiting for the write lock
current reader count: 0
Writer 1 finished
Writer 2 acquired the lock
Writer 2 finished
当 锁定 遇上 await
如果 在
lock块中 使用await关键字,会得到编译错误
原因:
在
await完成之后, 这个方法可能会在另一个线程中运行
而
lock关键字需要 在同一个线程中 获取锁和释放锁
private static object s_syncLock = new object();
static async Task IncorrectLockAsync()
{
lock (s_syncLock)
{
Console.WriteLine($"{nameof(IncorrectLockAsync)} started");
await Task.Delay(500); // compiler error: cannot await in the body of a lock statement
Console.WriteLine($"{nameof(IncorrectLockAsync)} ending");
}
}
使用
Monitor代替lock也不行
lock是基于Monitor的Monitor也需要在 它获取锁 的同一个线程中 释放锁
使用
Mutex也不行
Mutex为一个线程授予了一个锁,从不同的线程中释放锁是不可能的
解决办法:
使用
Semaphore和SemaphoreSlim可以
- 它可以 在不同的线程中 释放信号量
示例1: SemaphoreSlim
- 使用
SemaphoreSlim对象的WaitAsync()获得一个信号量 SemaphoreSlim构造函数传递参数 1, 因此对信号量的等只授予 1次
private static SemaphoreSlim s_asyncLock = new SemaphoreSlim(1);
static async Task LockWithSemaphore(string title)
{
Console.WriteLine($"{title} waiting for lock");
await s_asyncLock.WaitAsync();
try
{
Console.WriteLine($"{title} {nameof(LockWithSemaphore)} started");
await Task.Delay(500);
Console.WriteLine($"{title} {nameof(LockWithSemaphore)} ending");
}
finally
{
s_asyncLock.Release();
}
}
- 启动6个
Task, 并发调用 上面的方法
static async Task RunUseSemaphoreAsync()
{
Console.WriteLine(nameof(RunUseSemaphoreAsync));
string[] messages = { "one", "two", "three", "four", "five", "six" };
Task[] tasks = new Task[messages.Length];
for (int i = 0; i < messages.Length; i++)
{
string message = messages[i];
tasks[i] = Task.Run(async () =>
{
await LockWithSemaphore(message);
});
}
await Task.WhenAll(tasks);
Console.WriteLine();
}
输出:
RunUseSemaphoreAsync
four waiting for lock
two waiting for lock
six waiting for lock
four LockWithSemaphore started
three waiting for lock
five waiting for lock
one waiting for lock
four LockWithSemaphore ending
two LockWithSemaphore started
two LockWithSemaphore ending
six LockWithSemaphore started
six LockWithSemaphore ending
three LockWithSemaphore started
three LockWithSemaphore ending
five LockWithSemaphore started
five LockWithSemaphore ending
one LockWithSemaphore started
one LockWithSemaphore ending
示例2: 封装SemaphoreSlim
为了更容易地使用锁:封装完 就可以使用
using语句来 锁定和释放信号量
public sealed class AsyncSemaphore
{
private class SemaphoreReleaser : IDisposable
{
private SemaphoreSlim _semaphore;
public SemaphoreReleaser(SemaphoreSlim semaphore) =>
_semaphore = semaphore;
public void Dispose() => _semaphore.Release();
}
private SemaphoreSlim _semaphore;
public AsyncSemaphore() =>
_semaphore = new SemaphoreSlim(1);
public async Task<IDisposable> WaitAsync()
{
await _semaphore.WaitAsync();
return new SemaphoreReleaser(_semaphore) as IDisposable;
}
}
private static AsyncSemaphore s_asyncSemaphore = new AsyncSemaphore();
static async Task UseAsyncSemaphore(string title)
{
using (await s_asyncSemaphore.WaitAsync())
{
Console.WriteLine($"{title} {nameof(LockWithSemaphore)} started");
await Task.Delay(500);
Console.WriteLine($"{title} {nameof(LockWithSemaphore)} ending");
}
}
输出:
RunUseAsyncSempahoreAsync
three LockWithSemaphore started
three LockWithSemaphore ending
two LockWithSemaphore started
two LockWithSemaphore ending
four LockWithSemaphore started
four LockWithSemaphore ending
one LockWithSemaphore started
one LockWithSemaphore ending
five LockWithSemaphore started
five LockWithSemaphore ending
six LockWithSemaphore started
six LockWithSemaphore ending