使用 ConcurrentExclusiveSchedulerPair 作为异步 ReaderWriterLock 等价物
Using ConcurrentExclusiveSchedulerPair as an async ReaderWriterLock equivalent
我的应用程序有几个异步方法可以访问磁盘上的文件。当我了解到在这种情况下不能使用普通的 ReaderWriterLockSlim
时,我去寻找一个等价物。找到了看起来很有前途的 ConcurrentExclusiveSchedulerPair
。我阅读了一些关于它的启发性博客文章,我开始认为这对我来说可能是正确的选择。
所以我将所有读取任务更改为使用并发调度程序,将写入任务更改为使用独占调度程序。但是,事实证明我仍然收到 IOException
s 说文件正在使用中。这是我的代码的简化(但仍然失败)版本:
public async Task<IEnumerable<string>> Run()
{
var schedulers = new ConcurrentExclusiveSchedulerPair();
var exclusiveFactory = new TaskFactory(schedulers.ExclusiveScheduler);
var concurrentFactory = new TaskFactory(schedulers.ConcurrentScheduler);
var tasks = new List<Task>();
for (var i = 0; i < 40; ++i)
{
// Create some readers and (less) writers
if (i % 4 == 0)
{
var writeTask = exclusiveFactory.StartNew(WriteToFile).Unwrap();
tasks.Add(writeTask);
}
else
{
var readTask = concurrentFactory.StartNew(ReadFromFile).Unwrap();
tasks.Add(readTask);
}
}
await Task.WhenAll(tasks);
return _contents;
}
private async Task ReadFromFile()
{
using (var fileStream = new FileStream("file.txt", FileMode.OpenOrCreate, FileAccess.Read))
using (var reader = new StreamReader(fileStream))
{
await Task.Delay(500); // some other work
_contents.Add(await reader.ReadToEndAsync());
}
}
private async Task WriteToFile()
{
using (var fileStream = new FileStream("file.txt", FileMode.OpenOrCreate, FileAccess.Write))
using (var writer = new StreamWriter(fileStream))
{
await Task.Delay(500); // some other work
await writer.WriteLineAsync("Lorem ipsum");
}
}
然后我发现Stephen Cleary's blog post有红框警告:
When an asynchronous method awaits, it returns back to its context. This means that ExclusiveScheduler is perfectly happy to run one task at a time, not one task until it completes. As soon as an asynchronous method awaits, it’s no longer the “owner” of the ExclusiveScheduler. Stephen Toub’s async-friendly primitives like AsyncLock use a different strategy, allowing an asynchronous method to hold the lock while it awaits.
"One task at a time, not one task until it completes" - 现在这有点令人困惑。这是否意味着 ConcurrentExclusiveSchedulerPair
不是这种情况的正确选择,还是我使用不正确?也许应该在这里使用 AsyncLock
(为什么它不是框架的一部分)?或者一个普通的旧 Semaphore
就足够了吗(我知道那时我不会得到 reader-writer 部门,但也许没关系)?
async
方法[1] 的一种思考方式是在每个 await
点将它们拆分为任务。要使用您的示例:
private async Task WriteToFile()
{
using (var fileStream = new FileStream("file.txt", FileMode.OpenOrCreate, FileAccess.Write))
using (var writer = new StreamWriter(fileStream))
{
await Task.Delay(500); // some other work
await writer.WriteLineAsync("Lorem ipsum");
}
}
概念上分为三个任务:
- 创建
fileStream
和 writer
并启动 Task.Delay
(一些其他工作)的任务。
- 观察
Task.Delay
结果并启动WriteLineAsync
的任务。
- 观察
WriteLineAsync
的结果并处理writer
和fileStream
的任务。
ConcurrentExclusiveSchedulerPair
是一个任务调度器,所以它的语义只在有任务运行ning代码时才适用。当 WriteToFile
是 运行 和 ExclusiveScheduler
时,它持有独占调度程序锁,而任务 (1) 是 运行ning。一旦 Task.Delay
代码开始,任务 (1) 就完成了,它释放了独占调度程序锁。在 500
毫秒延迟期间,独占调度程序锁 未 持有。一旦延迟完成,任务 (2) 就准备就绪并排队到 ExclusiveScheduler
。然后它获取独占调度程序锁并完成它的(少量)工作。当任务 (2) 完成时,它还会释放独占调度程序锁。等等
任务计划程序旨在处理同步任务。在 await
中对它们有一些支持(即,TaskScheduler.Current
被自动捕获并用于从 await
恢复),但通常它们在处理异步任务时没有预期的语义.每个 await
实际上是在告诉任务调度器 "this (sub)task is done".
Does this mean the ConcurrentExclusiveSchedulerPair is not the right choice for this situation?
是的。 ConcurrentExclusiveSchedulerPair
不是正确的选择。
Perhaps an AsyncLock should be used here instead (why isn't it a part of the framework)?
SemaphoreSlim
支持异步锁定(使用更笨拙的语法)。但它可能不在 WP81 上——我不记得了(WaitAsync
是后来添加的)。要支持这么旧的平台,您必须使用 AsyncEx v4 或 copy/paste Stephen Toub's code.
I understand I wouldn't get the reader-writer division then, but maybe it's ok?
这不仅没问题,而且几乎可以肯定是更可取的。当你真的只需要一个轻量级锁时使用 reader/writer 锁是一个非常常见的错误。特别是,仅仅因为某些代码具有读取语义而其他代码具有写入语义不是使用 RWL 的充分理由。
[1] 出于效率原因,async
方法在 await
点被分解为代码块,但这些单独的代码块实际上并未被 Task
对象包装除非 TaskScheduler
存在。
我的应用程序有几个异步方法可以访问磁盘上的文件。当我了解到在这种情况下不能使用普通的 ReaderWriterLockSlim
时,我去寻找一个等价物。找到了看起来很有前途的 ConcurrentExclusiveSchedulerPair
。我阅读了一些关于它的启发性博客文章,我开始认为这对我来说可能是正确的选择。
所以我将所有读取任务更改为使用并发调度程序,将写入任务更改为使用独占调度程序。但是,事实证明我仍然收到 IOException
s 说文件正在使用中。这是我的代码的简化(但仍然失败)版本:
public async Task<IEnumerable<string>> Run()
{
var schedulers = new ConcurrentExclusiveSchedulerPair();
var exclusiveFactory = new TaskFactory(schedulers.ExclusiveScheduler);
var concurrentFactory = new TaskFactory(schedulers.ConcurrentScheduler);
var tasks = new List<Task>();
for (var i = 0; i < 40; ++i)
{
// Create some readers and (less) writers
if (i % 4 == 0)
{
var writeTask = exclusiveFactory.StartNew(WriteToFile).Unwrap();
tasks.Add(writeTask);
}
else
{
var readTask = concurrentFactory.StartNew(ReadFromFile).Unwrap();
tasks.Add(readTask);
}
}
await Task.WhenAll(tasks);
return _contents;
}
private async Task ReadFromFile()
{
using (var fileStream = new FileStream("file.txt", FileMode.OpenOrCreate, FileAccess.Read))
using (var reader = new StreamReader(fileStream))
{
await Task.Delay(500); // some other work
_contents.Add(await reader.ReadToEndAsync());
}
}
private async Task WriteToFile()
{
using (var fileStream = new FileStream("file.txt", FileMode.OpenOrCreate, FileAccess.Write))
using (var writer = new StreamWriter(fileStream))
{
await Task.Delay(500); // some other work
await writer.WriteLineAsync("Lorem ipsum");
}
}
然后我发现Stephen Cleary's blog post有红框警告:
When an asynchronous method awaits, it returns back to its context. This means that ExclusiveScheduler is perfectly happy to run one task at a time, not one task until it completes. As soon as an asynchronous method awaits, it’s no longer the “owner” of the ExclusiveScheduler. Stephen Toub’s async-friendly primitives like AsyncLock use a different strategy, allowing an asynchronous method to hold the lock while it awaits.
"One task at a time, not one task until it completes" - 现在这有点令人困惑。这是否意味着 ConcurrentExclusiveSchedulerPair
不是这种情况的正确选择,还是我使用不正确?也许应该在这里使用 AsyncLock
(为什么它不是框架的一部分)?或者一个普通的旧 Semaphore
就足够了吗(我知道那时我不会得到 reader-writer 部门,但也许没关系)?
async
方法[1] 的一种思考方式是在每个 await
点将它们拆分为任务。要使用您的示例:
private async Task WriteToFile()
{
using (var fileStream = new FileStream("file.txt", FileMode.OpenOrCreate, FileAccess.Write))
using (var writer = new StreamWriter(fileStream))
{
await Task.Delay(500); // some other work
await writer.WriteLineAsync("Lorem ipsum");
}
}
概念上分为三个任务:
- 创建
fileStream
和writer
并启动Task.Delay
(一些其他工作)的任务。 - 观察
Task.Delay
结果并启动WriteLineAsync
的任务。 - 观察
WriteLineAsync
的结果并处理writer
和fileStream
的任务。
ConcurrentExclusiveSchedulerPair
是一个任务调度器,所以它的语义只在有任务运行ning代码时才适用。当 WriteToFile
是 运行 和 ExclusiveScheduler
时,它持有独占调度程序锁,而任务 (1) 是 运行ning。一旦 Task.Delay
代码开始,任务 (1) 就完成了,它释放了独占调度程序锁。在 500
毫秒延迟期间,独占调度程序锁 未 持有。一旦延迟完成,任务 (2) 就准备就绪并排队到 ExclusiveScheduler
。然后它获取独占调度程序锁并完成它的(少量)工作。当任务 (2) 完成时,它还会释放独占调度程序锁。等等
任务计划程序旨在处理同步任务。在 await
中对它们有一些支持(即,TaskScheduler.Current
被自动捕获并用于从 await
恢复),但通常它们在处理异步任务时没有预期的语义.每个 await
实际上是在告诉任务调度器 "this (sub)task is done".
Does this mean the ConcurrentExclusiveSchedulerPair is not the right choice for this situation?
是的。 ConcurrentExclusiveSchedulerPair
不是正确的选择。
Perhaps an AsyncLock should be used here instead (why isn't it a part of the framework)?
SemaphoreSlim
支持异步锁定(使用更笨拙的语法)。但它可能不在 WP81 上——我不记得了(WaitAsync
是后来添加的)。要支持这么旧的平台,您必须使用 AsyncEx v4 或 copy/paste Stephen Toub's code.
I understand I wouldn't get the reader-writer division then, but maybe it's ok?
这不仅没问题,而且几乎可以肯定是更可取的。当你真的只需要一个轻量级锁时使用 reader/writer 锁是一个非常常见的错误。特别是,仅仅因为某些代码具有读取语义而其他代码具有写入语义不是使用 RWL 的充分理由。
[1] 出于效率原因,async
方法在 await
点被分解为代码块,但这些单独的代码块实际上并未被 Task
对象包装除非 TaskScheduler
存在。