使用ConcurrentExclusiveSchedulerPair作为异步ReaderWriterLock等效项

Mic*_*dak 3 .net c# task-parallel-library async-await

我的应用程序有几个异步方法可以访问磁盘上的文件.正如我所知,普通ReaderWriterLockSlim不能在这种情况下使用,我去寻找一个等价物.发现ConcurrentExclusiveSchedulerPair那看起来很有希望.我读了一些有关它的启发性博客文章,我开始认为这可能是我的正确选择.

所以我改变了所有的阅读任务,使用Concurrent调度程序并编写任务来使用Exclusive.然而,事实证明我还在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");
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我找到了Stephen Cleary的博客文章,并在一个红色框中发出警告:

当异步方法等待时,它返回其上下文.这意味着ExclusiveScheduler非常乐意一次运行一个任务,而不是一个任务,直到完成.一旦异步方法等待,它就不再是ExclusiveScheduler的"所有者".Stephen Toub的异步友好原语(如AsyncLock)使用不同的策略,允许异步方法在等待时保持锁定.

"一次完成一项任务,而不是一项任务,直到完成" - 现在这有点令人困惑.这是否意味着ConcurrentExclusiveSchedulerPair这种情况不是正确的选择,还是我错误地使用它?也许AsyncLock应该在这里使用(为什么它不是框架的一部分)?或者说一个普通的老人Semaphore就够了(我明白我当时不会得到读写师,但也许没关系)?

Ste*_*ary 10

思考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");
  }
}
Run Code Online (Sandbox Code Playgroud)

概念上分为三个任务:

  1. 创建fileStream和的任务writer,并启动Task.Delay(其他一些工作).
  2. 观察结果Task.Delay并开始的任务WriteLineAsync.
  3. 观察结果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或复制/粘贴Stephen Toub的代码.

我明白我不会得到读写器部门,但也许没关系?

它不仅可以,而且几乎可以肯定.当您真正需要轻量级锁时使用读取器/写入器锁是一个非常常见的错误.特别是,仅仅因为一些代码已经阅读语义和其他代码有写语义是不是一个足够的理由来使用RWL.

[1]出于效率原因,async方法在await点处被分解为代码块,但是Task除非TaskScheduler存在a ,否则那些单独的代码块实际上不会被对象包装.