共享变量上的C#多线程冲突

时间:2018-11-20 19:22:41

标签: c# multithreading thread-safety

我的问题如下: 我已经在自己的线程1中运行了GUI,并对其进行了一些配置设置。

配置本身将由不同类中的另一个线程2使用(仅具有读取访问权限),但是当我在GUI中应用时,应尽快更新。

该配置包含bool和Dictionaries之类的变量。

解决这个问题的安全方法是什么?

到目前为止,我的想法是: 在某些静态Instance变量中包含当前配置的类:

public class Config
{
   static Config Instance;
   int a;
   Dictionary<int, int> b;
   ...
   public void update(Config newConfig) {}
}

,因此线程2中的所有类始终可以访问最新设置。

当我在GUI中按下诸如“应用”按钮之类的按钮以应用最新设置时,就会调用更新功能。

但是线程安全性如何?如何仅在不读取线程2时设置变量,而在设置变量时使线程2等待?我没有在c#中使用多线程的经验,我知道它可能必须在某些线程使用时锁定变量。还是有其他一些聪明的方法来实现它?

2 个答案:

答案 0 :(得分:0)

如果您有一个Config的共享实例,并且所有实例都使用该实例,则存在其他线程在读取属性时可能会更改属性的风险。为防止这种情况,您可以lock访问单个属性。但这不是一个愉快的想法。相反,最好是将您不得不担心的情况减少到最少。

第一步是让您的其他线程创建Config的新实例,而不是全部共享。您的GUI(表单?)可以接收Config的全新实例。

如果您有类似的事情(例如,例如-一个属性是否应该是公共的,或者您的类如何接收 Config的实例,则完全是另外一个问题。)

public Config MyConfigInstance { get; set; }

如果通过提供具有不同值的Config的不同实例来设置该属性,则这是原子操作。您不必担心您的班级将在正在更新的引用中途访问MyConfigInstance

这可能就是您所需要的。但是仍然存在错误的余地。现在,您的课程有了对Config的新实例的引用。但是还有哪些其他线程可以访问相同的引用,并且仍然可以更改它?至少将它传递给此类的线程仍然可以访问。如果该线程在该实例周围传递该实例,而现在其他类对该实例进行了引用,该怎么办?

这可能不是您的应用程序中的问题。也许您确切地知道哪些线程可以访问哪些类的实例。但是,如果事情变得更加复杂,我们宁愿避开这个问题,也不愿跟踪它。

为此,如果多个线程将共享一个类的实例,则通常最好使该类不可变。就像这样:

public class MyImmutableClass
{
    public MyImmutableClass(string stringValue, int intValue)
    {
        StringValue = stringValue;
        IntValue = intValue;
    }

    public string StringValue { get; }
    public int IntValue { get; }
}

它的属性是在创建时设置的,不能更改。此时,您不必担心有多少个线程引用了该类的实例,因为它们都无法更改它。您可以将对一个实例的引用替换为对新实例的引用,但这不会影响其他引用。

不可变类的另一个好处是,如果仅在构造函数中设置值,则可以验证它们。像上面的示例一样,如果stringValue为null,则可能引发异常。这样,您始终知道该类处于有效状态。创建处于无效状态的实例是不可能的。

我们倾向于创建可变类,因为创建读/写属性比创建构造函数要快。但是,如果我们打算将实例传递给不同的线程,那么有时最好从不变性开始,这迫使我们考虑并控制多个线程可以或不能完成的事情。

答案 1 :(得分:-1)

您正在寻找单例设计模式:

public sealed class Config
{
    private static Config instance = null;
    private static readonly object padlock = new object();

    #region ConfigProperties
    //put public properties here to represent config values
    public int a { get; set; }
    public Dictionary<int, int> b { get; set; }
    #endregion ConfigProperties

    Config()
    {
        ReadConfig();
    }

    public static Config Instance
    {
        get
        {
            lock (padlock)
            {
                if (instance == null)
                {
                    instance = new Config();
                }
                return instance;
            }
        }
    }
    public void ReadConfig()
    {
        //read from your config file and set properties
    }
    public void SaveConfig()
    {
        //write properties back into your config file
    }
}

此类是线程安全的,这意味着您可以在所有配置属性后使用Config.Instance.,并且可以安全地访问它们而不会引起跨线程问题。

有更优雅的解决方案和更详尽的解释here