【问题标题】:Why my code doesn't work correctly in synchronization?为什么我的代码在同步时不能正常工作?
【发布时间】:2020-11-17 21:20:48
【问题描述】:

我正在使用此类来防止两个应用程序(一个 Windows 应用程序和一个在同一操作系统上运行的 Web 应用程序)同时使用共享文件。但我收到错误“对象同步方法是从未同步的代码块中调用的。”

    class SharedMutex : IDisposable
    {
        readonly Mutex mutex;

        /// <summary>
        /// This function will wait if other thread has owned the mutex
        /// </summary>
        /// <param name="name"></param>
        public SharedMutex(string name)
        {
            bool m = Mutex.TryOpenExisting(name, out mutex);
            if (m)
            {
                mutex.WaitOne();
            }
            else
            {
                mutex = new Mutex(true, name);
            }
        }

        public const string Logs = @"Global\Logs";

        public void Dispose()
        {
            mutex.ReleaseMutex();
            mutex.Dispose();
        }
    }

这就是我使用这个类的方式

using (new SharedMutex(SharedMutex.Logs))
                {
                   ///access the shared file
                }

这个类存在于两个项目中。

注意:我不是在寻找访问文件问题的解决方案,我需要知道为什么我的代码有问题。因为我也想将此代码用于其他目的。 谢谢。

【问题讨论】:

  • 您的“访问共享文件”是否包含await
  • "互斥锁具有线程亲和性,互斥锁的所有者是线程。获取它的线程也必须是调用 ReleaseMutex() 的线程。这就是您的代码炸弹的原因." - stackoverflow.com/a/9017728/43846
  • 这个错误意味着你试图从另一个线程释放互斥锁,而不是一个拥有所有权的线程。没有minimal reproducible example,很难添加更多内容。
  • @Sinatr 应该重新打开吗?
  • 我认为问题是TryOpenExisting 之间的竞争条件,它返回false 并试图创建具有else 所有权的互斥锁。你可能不会成功。您没有按照重复的答案处理这种情况。

标签: c# multithreading thread-safety mutex


【解决方案1】:

我认为这可能是由竞争条件引起的(正如问题 cmets 中的 /u/Sinatr 所建议的那样)。

以下代码重现了该问题:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main()
        {
            var t1 = Task.Run(func1);
            var t2 = Task.Run(func2);

            Task.WaitAll(t1, t2);

            Console.WriteLine("Done. Press <ENTER>");
            Console.ReadLine();
        }

        static void func1()
        {
            using (new SharedMutex("test"))
            {
                Thread.Sleep(2000);
            }
        }

        static void func2()
        {
            using (new SharedMutex("test"))
            {
                Thread.Sleep(1000);
            }
        }
    }

    class SharedMutex : IDisposable
    {
        readonly Mutex mutex;

        public SharedMutex(string name)
        {
            bool m = Mutex.TryOpenExisting(name, out mutex);

            if (m)
            {
                mutex.WaitOne();
            }
            else
            {
                Thread.Sleep(10); // Simulate a short delay.
                mutex = new Mutex(true, name);
            }
        }

        public void Dispose()
        {
            mutex.ReleaseMutex();
            mutex.Dispose();
        }
    }
}

注意短暂的延迟Thread.Sleep(10),这足以在我的系统上引发异常,运行调试版本。可能必须增加延迟才能在其他系统上引发异常。

如果这确实是问题,这是解决问题的方法:

class SharedMutex : IDisposable
{
    readonly Mutex mutex;

    public SharedMutex(string name)
    {
        mutex = new Mutex(false, name);
        mutex.WaitOne();
    }

    public void Dispose()
    {
        mutex.ReleaseMutex();
        mutex.Dispose();
    }
}

你并不关心它是否是新创建的。 Mutex 构造函数会为您处理这些问题。

即使这不是问题,我仍然认为您应该使用上面的实现,因为它没有潜在的竞争条件。

正如the documentation for the constructor Mutex(bool, string) 所说:

如果 name 不为 null 并且 initialOwned 为 true,则调用线程 仅当命名系统互斥体被创建时才拥有互斥体 这个电话。由于没有机制来确定是否 已创建命名系统互斥锁,最好为 InitialOwned 调用此构造函数重载时。

【讨论】:

  • 谢谢。我不知道程序可能不会等待 new Mutex(true, name); 上的其他线程;我也不知道比赛条件到底是什么意思。你能解释一下或发个链接吗?
  • @DadkhahDadkhah 有an article about race conditions on Wikipedia,但基本上这意味着当两个线程几乎同时调用它时,有些代码可能会出错。
猜你喜欢
  • 1970-01-01
  • 2017-02-06
  • 1970-01-01
  • 1970-01-01
  • 2019-06-10
  • 1970-01-01
  • 2017-02-03
  • 2016-10-03
  • 1970-01-01
相关资源
最近更新 更多