我的应用程序有几个异步方法可以访问磁盘上的文件。当我了解到在这种情况下不能使用普通的ReaderWriterLockSlim时,我就去寻找一个类似的。找到了看起来很有希望的ConcurrentExclusiveSchedulerPair。我读了一些关于它的有启发性的博客文章,我开始认为这可能是我的正确选择。
因此,我将所有读取任务更改为使用并发调度程序,并将写入任务更改为使用独占任务。然而,事实证明,我仍然得到IOException说该文件正在使用。下面是我的代码的一个简化版本(并且仍然失败):
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");
}
}然后我在一个红色的盒子里找到了一个警告斯蒂芬·克利里的博客文章:
当异步方法等待时,它会返回到它的上下文。这意味着ExclusiveScheduler非常乐意一次运行一个任务,而不是在完成之前运行一个任务。异步方法一旦等待,它就不再是ExclusiveScheduler的“所有者”。Stephen的异步友好原语(如AsyncLock )使用了不同的策略,允许异步方法在等待锁时保持锁。
“一次一个任务,在它完成之前没有一个任务”--现在这有点让人困惑。这是否意味着对于这种情况,ConcurrentExclusiveSchedulerPair不是正确的选择,还是我使用它不正确?也许这里应该使用AsyncLock (为什么它不是框架的一部分)?或者一个普通的老Semaphore就足够了(我知道那时我不会让读者-作家部门,但也许这是可以的)?
发布于 2018-01-20 02:09:20
async methods1的一种思维方式是在每个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是一个任务调度程序,所以它的语义只适用于有一个任务运行的代码。当WriteToFile与ExclusiveScheduler一起运行时,它在任务(1)运行时保持排它的调度程序锁。一旦Task.Delay代码启动,该任务(1)就完成了,并释放排它的调度程序锁。在500毫秒延迟期间,排它的调度程序锁是而不是。一旦该延迟完成,任务(2)就准备就绪,并排队到ExclusiveScheduler。然后,它使用排它的调度程序锁,并完成其(少量)工作量。当任务(2)完成时,它还释放排它的调度程序锁。等。
任务调度程序设计用于处理同步任务。在await中有一些对它们的支持(例如,TaskScheduler.Current被自动捕获并用于从await恢复),但是通常它们在处理异步任务时没有预期的语义。每个await实际上都在告诉任务调度程序“完成了这个(子)任务”。
这是否意味着ConcurrentExclusiveSchedulerPair不是这种情况下的正确选择?
是。ConcurrentExclusiveSchedulerPair不是正确的选择。
也许这里应该使用AsyncLock (为什么它不是框架的一部分)?
SemaphoreSlim支持异步锁定(使用更笨拙的语法)。但它可能不会出现在WP81上--我不记得了(后来添加了WaitAsync)。要支持这么老的平台,您必须使用AsyncEx v4或复制/粘贴斯蒂芬·图布密码。
我知道我不想让读者和作家分开,但也许没关系?
这不仅是好的,而且几乎肯定是可取的。在您真正需要轻量级锁时使用读取器/写入器锁是一个非常常见的错误。特别是,仅仅因为有些代码具有读取语义,而其他代码具有写语义,这并不是使用RWL的充分理由。
1出于效率原因,async方法在await点被分解为代码块,但是那些单独的代码块实际上并不被Task对象包装,除非存在TaskScheduler。
https://stackoverflow.com/questions/48346489
复制相似问题