【问题标题】:Named Mutex with await用 await 命名的 Mutex
【发布时间】:2014-04-18 11:25:52
【问题描述】:

因此我不能在async 中使用线程仿射锁 - 在运行多个进程时如何保护我的资源?

例如,我有两个使用以下任务的进程:

 public async Task<bool> MutexWithAsync()
 {
     using (Mutex myMutex = new Mutex(false, "My mutex Name"))
     {
         try
         {
             myMutex.WaitOne();
             await DoSomething();
             return true;
         }
         catch { return false; }
         finally { myMutex.ReleaseMutex(); }
     }
 }

如果受 Mutex 保护的方法是同步的,那么上面的代码将有效,但使用 async 我会得到:

对象同步方法是从未同步的代码块中调用的。

那么命名互斥锁对异步代码没用吗?

【问题讨论】:

  • @Romasz:有可能采用不同的架构吗?我正在考虑一个代表其他进程执行请求的专用资源所有者进程。
  • @Noseratio 谢谢你的链接,它帮助我理解了一些东西,破坏了我的信号量想法;)
  • @StephenCleary 这可能是一个解决方案,但目前我正在研究 WP,它执行后台任务的可能性有限。也许我可以为此目的伪造代理人,但我想避免这种情况。谢谢你的帮助,你的一句话让我有了另一个想法。不过我会尝试更多地了解 Mutex 和 async。

标签: c# multithreading asynchronous async-await


【解决方案1】:

您必须确保在某个线程上一致地访问互斥锁。您可以通过多种方式做到这一点:

  1. 不要在持有互斥锁的关键部分使用 await
  2. 在只有一个线程的 TaskScheduler 上调用互斥锁

可能是这样的:

await Task.Factory.StartNew(() => mutex.WaitOne(), myCustomTaskScheduler);

或者,您使用同步代码并将所有内容移至线程池。如果您只能访问 DoSomething 的异步版本,请考虑在其结果上调用 Task.Wait。你会在这里遭受轻微的低效率。应该没问题。

【讨论】:

  • 我必须考虑您的答案并尝试一些事情。问题是:我必须在持有 Mutex 的同时调用 await(这是一个应该被保护的重要部分 - 例如访问文件)并且我无权访问 DoSomething。我认为当我调用await DoSomething 时,默认情况下ConfigureAwait(true) 会捕获上下文,完成任务后,其余部分会在捕获的上下文中继续。
  • 是的,但捕获的上下文很可能意味着“任何线程池线程”。鉴于我所掌握的信息,我认为所有 3 种提议的解决方案都对您有用。如果您需要更多信息,请告诉我。
  • 所以我现在了解到,当我们从 await 回来时,我们会回到以前的上下文,但我们可能处于不同的线程中。我已经对该主题进行了一些研究:至于您的第一种方法-我尝试使用 await 打开一个流,并且我想打开该打开,因此必须在持有 Mutex 的同时完成(IMO)。第二个提案是不同的问题 - within RT 很难将 TaskScheduler 限制为一个线程 - 我考虑过 AutoResteEvent = 但我不确定它是否会保证一个线程......
  • 至于第三个选项 - 我已经实现它,似乎工作。据我了解,这使我的代码更同步,但打开流不会花费太多时间,因此正如您所说,这是一个小问题。
  • @Romasz 关于(2):您可以通过使用任务而不是线程来实现相同的效果。您需要编写一个由线程池任务支持的 TaskScheduler(设置了 LongRunning 选项)。关于 (3):您可以使用 Task.Run 包装所有同步等待以将其移出 UI 线程。这样您就可以保持 100% 的响应速度。唯一的缺点就是你烧掉了一个线程池线程的内存,还不错。
【解决方案2】:

我在异步方法中使用命名 Mutex 来控制只有一个进程调用它。另一个进程检查命名的 Mutex 并在它无法创建新的 named Mutex 时退出。

我可以在异步方法中使用命名互斥锁,因为操作系统只保证/控制操作系统中命名对象的一个​​实例。另外,我不使用应该在线程上调用的 WaitOne/Release。

public async Task<bool> MutexWithAsync()
{
    // Create OS-wide named object. (It will not use WaitOne/Release)
    using (Mutex myMutex = new Mutex(false, "My mutex Name", out var owned))
    {
        if (owned)
        {
            // New named-object was created. We own it.
            try
            {
                await DoSomething();
                return true;
            }
            catch
            {
                return false;
            }
        }
        else
        {
            // The mutex was created by another process.
            // Exit this instance of process.
        }
    }
}

【讨论】:

  • 问题是如果使用默认同步上下文,await DoSomething(); 可以在不同的线程上继续。你在非 UI 线程上试过吗?
  • 是的,它可以在不同的线程上继续。我的主要想法是我不同步同一进程的线程。我创建命名内核对象。一个命名的内核对象只能创建一次,第二次创建它会失败。就 C# 而言:如果创建了命名对象,则 owned 将是 true。如果创建命名对象失败,owned 将是false。为此,我可以使用/创建任何内核命名对象。 docs.microsoft.com/en-us/windows/desktop/sysinfo/kernel-objectsEvent、Mutex 和 Semaphore 都不错。
  • 现在我明白你的意思了,好主意。我只是在想这里是否有可能出现比赛条件,但找不到。
  • 这太棒了!看起来当进程退出时互斥锁会自动释放?
  • Felix,是的,因为它派生自具有 finalizer 的类。
【解决方案3】:

您可以使用二进制信号量代替互斥量。信号量不需要由获取它的同一线程释放。这里最大的缺点是如果应用程序崩溃或在 DoSomething() 中被终止,则信号量将不会被释放并且应用程序的下一个实例将挂起。见Abandoned named semaphore not released

 public async Task<bool> MutexWithAsync()
 {
     using (Semaphore semaphore = new Semaphore(1, 1, "My semaphore Name"))
     {
         try
         {
             semaphore.WaitOne();
             await DoSomething();
             return true;
         }
         catch { return false; }
         finally { semaphore.Release(); }
     }
 }

【讨论】:

  • 这是wekempf 在他的回答中所建议的。
【解决方案4】:

我为您提供了一个有趣的解决方案。现在没有时间提供代码示例,所以如果我的描述不够,请告诉我,我会尝试提供代码。

这里有两个问题。首先,正如您所指出的,AsyncMutex 没有线程亲和性。所以你不能用 Mutex 构建一个。但是,您可以使用计数为 1 的信号量构建一个,因为信号量也没有线程亲和性。在 C# 中,可以跨进程边界命名和使用 Semaphore 类。所以第一个问题很容易解决。

第二个问题是当你“锁定”这个 AsyncMutex 时不想使用阻塞调用。好吧,您可以使用 ThreadPool.RegisterWaitForSingleObject 注册一个回调,以便在信号量(a WaitHandle)发出信号时执行。这会进行“异步等待”。使用 TaskCompletionSource 用一些代码包装它,您可以相当轻松地在 AsyncMutex 上构建返回 WaitAsync 方法的任务。这两个想法应该可以相当容易地实现一个名为 AsyncMutex 的跨进程,可以在 C# 中使用。

请记住,与您会发现的其他 AsyncMutex 实现一样,这不是递归互斥锁(同一个“线程”可以多次锁定互斥锁,只要它解锁互斥锁的次数相同),所以在代码中必须注意不要导致死锁。

【讨论】:

  • 您说得对,Semaphore 可用于此目的。尽管如此,为了我的简单目的和简短的过程,让它在单线程上同步运行更容易。
  • 如果你想要更轻量的东西并且不需要命名或跨进程同步,你也可以使用 SemaphoreSlim。
【解决方案5】:

这个守卫与 async/await 完美配合:

public sealed class AsyncLock : IDisposable
{
    readonly AutoResetEvent Value;

    public AsyncLock(AutoResetEvent value, int milliseconds = Timeout.Infinite)
    {
        if (value == null)
            throw new ArgumentNullException("value");

        value.WaitOne(milliseconds);
        Value = value;
    }

    void IDisposable.Dispose()
    {
        Value.Set();
    }
}

private async Task TestProc()
{
    var guard = new AutoResetEvent(true); // Guard for resource

    using (new AsyncLock(guard)) // Lock resource
    {
        await ProcessNewClientOrders(); // Use resource
    } // Unlock resource
}

【讨论】:

  • 这是同步等待取出锁,不是异步等待取出锁。
  • 2 服务:请阅读任务。这与异步等待无关。如果你需要——问我,我会给你。
  • 这是一个异步方法;当然它应该异步等待。如果该方法正在同步等待取出锁,则该方法将不再是异步的。
猜你喜欢
  • 1970-01-01
  • 2016-11-13
  • 2014-06-03
  • 2020-04-21
  • 1970-01-01
  • 1970-01-01
  • 2015-03-12
  • 2017-03-28
  • 2022-12-13
相关资源
最近更新 更多