【问题标题】:How do I keep my Async method thread safe?如何保持我的异步方法线程安全?
【发布时间】:2015-08-19 18:22:33
【问题描述】:

我需要在我的 Windows 通用应用程序中编写一个方法来写入 SD 卡。在下面的方法中,如何确保两个线程不会同时尝试写入同一个文件?

public async void WriteToCard(string strFileName, IEnumerable<string> listLinesToWrite)
{
    IStorageItem item = await folder.GetItemAsync(strFileName);
    StorageFile file = (StorageFile)item;

    await Windows.Storage.FileIO.WriteLinesAsync(file, listLinesToWrite);
}

【问题讨论】:

  • 最简单的方法:一个接一个地写(如果你尝试并行写,很可能你不会变得更快) - 如果不是你必须自己照顾它(没有办法给来自这个 sn-p 的好建议)
  • Carsten - 我不是在寻找性能提升。我担心的是这个方法在同一个文件上被调用了两次。我将使用我在使用锁的旧应用程序中使用的代码来编辑我的问题。我的理解是你不应该在异步方法中使用锁。
  • 是的,它们使用起来很棘手(正如 dcastr 所说的那样)-我只是将所有写入排队并一个接一个地写入(TPL-dataflow 中有预构建的东西,你肯定会如果您寻找 agents 就可以找到一些东西 - 当然您可以使用 Queue, ConcurrentQueue, ...)
  • @Carsten 在提到 TPL Dataflow 库时可能指的是ActionBlock&lt;T&gt;
  • 是的,就是这样(我不记得了,因为你在 F# 中有更好的选择;))

标签: c# multithreading asynchronous win-universal-app iot


【解决方案1】:

您可以使用AsyncEx 库,其中包含AsyncLock

假设不会有大量文件名,您可以使用 ConcurrentDictionary 将文件名与锁相关联 - 否则,字典大小可能会变得不受约束。

private readonly locks = new ConcurrentDictionary<string, AsyncLock>();

public async void WriteToCard(string strFileName, IEnumerable<string> listLinesToWrite)
{
    var lock = locks.GetOrAdd(strFileName, () => new AsyncLock());

    using (await lock.LockAsync())
    {
        IStorageItem item = await folder.GetItemAsync(strFileName);
        StorageFile file = (StorageFile)item;

        await Windows.Storage.FileIO.WriteLinesAsync(file, listLinesToWrite);
    }
}

【讨论】:

  • 这将只允许写一个 thread 在任何时候写入一个文件......所以就像在老式 for 循环中执行它们一样...... .
  • @Carsten 假设写入是由同一个线程以确定的顺序完成的。在这种情况下,根本不需要同步,这个问题没有任何意义。如果写入是由多个线程以不确定的方式完成的,那么这是需要的。
  • 我只能猜测,因为它不是 100% 清楚 - 但我认为他想写入多个不同的文件(可能不止一次) - 所以你只需要注意你不'不要同时写入同一个文件 - 其余的可以通过几个并行线程来工作
  • @Brandon 这正是我想说的
  • 只要您对键使用弱引用,就无需担心数百万个文件。 ConditionalWeakTable 是一个标准选项,但列表性能可能会成为瓶颈(现在可能有标准解决方案没有这个缺点),但至少你没有内存泄漏。
【解决方案2】:

您可以保留一个带有ConcurrentDictionary 的映射,它将每个文件映射到一个SemaphoreSlim。然后,根据您要写入的文件位置获取每个信号量:

private ConcurrentDictionary<string, SemaphoreSlim> fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>();

public async Task WriteToCardAsync(string strFileName, IEnumerable<string> listLinesToWrite)
{
   var semaphoreSlim = fileLocks.GetOrAdd(strFileName, new SemaphoreSlim(1, 1));

   await semaphoreSlim.WaitAsync();
   try
   {
       IStorageItem item = await folder.GetItemAsync(strFileName);
      StorageFile file = (StorageFile)item;

        await Windows.Storage.FileIO.WriteLinesAsync(file, listLinesToWrite);
   }
   finally
   {
       semaphoreSlim.Release();
   }
}

旁注 - 使用 async Task 而不是 async void。我还在方法中添加了Async 后缀。

【讨论】:

  • 这里实际上不需要Lazy。将字典直接映射到信号量就可以了。如果您担心创建额外的信号量(这不应该是一个大问题,但仍然如此),只需使用采用工厂委托而不是对象本身的 GetOrAdd 的重载。
  • @Yuval Itzchakov 感谢您的精彩回答。 Lazy 的目的是防止创建 ConcurrentDictionary 直到需要节省资源?
  • @BillGreer 字典是在第一次使用此类时创建的,因此与它无关。
  • 只是想把我的头缠在 Lazy 身上。我查看了 MSDN 文档,但我仍然感到困惑 :)
  • @BillGreer 就像我在第一条评论中所说的那样;在这种情况下是多余的。它没有害处,只是没有完成任何重要的事情。
猜你喜欢
  • 2021-11-06
  • 1970-01-01
  • 1970-01-01
  • 2021-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多