是使用c#扩展方法线程安全读取XElement值?

时间:2016-07-23 19:13:06

标签: c# .net

在多线程环境中这个扩展方法线程安全吗?

class Program
{
    public static object lockObject = new object();
    static void Main(string[] args)
    {
        List<Student> studentCollection = new List<Student>();

        string xml = "<root>";
        xml += "<student><name>stud1</name><address>street1</address></student>";
        xml += "<student><name>stud2</name><address>street2</address></student>";
        xml += "<student><name>stud3</name><address>street3</address></student>";
        xml += "</root>";

        Parallel.For(0, 10000, index =>
        {
            XElement xmlElement = XElement.Parse(xml);
            var students = xmlElement.Descendants("student").ToList();
            students.ForEach(student =>
            {
                Student stud = new Student();
                stud.Name = student.GetValue("name");
                stud.Address = student.GetValue("address");
                lock (lockObject)
                {
                    studentCollection.Add(stud);
                }

            });

        });
    }    
}

public static class Extension
{
    public static string GetValue(this XElement xelement, string tagName)
    {
        var ele = xelement;

        if (ele != null && ele.Element(tagName) != null)
        {
            // I assume this as unsafe code. 
            return ele.Element(tagName).Value;
        }
        return string.Empty;
    }
}

1 个答案:

答案 0 :(得分:3)

您的扩展方法本质上是线程安全的,因为它不会修改其对象*的状态。

它不会调用任何方法或在传递给它的任何参数上设置任何可能改变其状态的属性。如果确实如此,你仍然可以认为它是线程安全的。问题是这些其他对象的方法和属性是否是线程安全的。

听起来你真的在问XElement的属性是否是线程安全的。他们不是。您无法安全地进行并发更新并读取其属性。但同样,那是XElement,而不是你的扩展方法。

如果有多个线程可能尝试对对象进行不安全的读/写操作,那么您需要使用lock或其他一些机制来控制对该对象的访问。一旦你将该对象传递给另一个方法,除非它有办法执行100%保证的原子操作,否则该方法无法做到。

我不建议深入了解某些特定代码行如何编译以确定原子是什么或不是原子的任何细节。如果Interlocked.Increment之类的内容用于此目的,那就太好了。或者,如果它类似于阅读Boolean,它明确记录了它是原子的。除此之外,我只是保护对象不被多个线程访问。

猜测您只是在阅读XML,这会使这个问题变得没有实际意义。如果您正在构建这些元素,那么您已经知道了该值,并且在您修改它的同时不需要从XML中读取它。只是猜测。

为清楚起见,这是 线程安全的扩展方法示例。

public static class StringTrimExtensions
{
     public static int CharactersTrimmed {get; private set;}

     public static string Trim(this string input)
     {
         var trimmed = input.Trim();
         CharactersTrimmed = CharactersTrimmed + (input.Length - trimmed.Length);
         return trimmed;
     }
}

(这是一种完全无用的方法。)但它不是线程安全的,因为它以非线程安全的方式修改自己的状态。因此,如果它从多个线程中使用,那么修剪字符的数量可能会被破坏。

但是如果我们更改它以确保对total的更新是原子的,那么该方法将变为线程安全。

public static class StringTrimExtensions
{
    private static int _charactersTrimmed;
    public static int CharactersTrimmed { get {return _charactersTrimmed;}  }

    public static string Trim(this string input)
    {
        var trimmed = input.Trim();
        var countOfTrimmedCharacters = input.Length - trimmed.Length;
        Interlocked.Add(ref _charactersTrimmed, countOfTrimmedCharacters);
        return trimmed;
    }
}

或者,为了演示,我们可以使用lock来防止多个线程同时更新CharactersTrimmed。 (如果我们只是添加这样的数字,那么Interlocked更有意义。)

public static class StringTrimExtensions
{
    public static int CharactersTrimmed { get; private set; }
    private static readonly object CharactersTrimmedLock = new object();

    public static string Trim(this string input)
    {
        var trimmed = input.Trim();
        lock (CharactersTrimmedLock)
        {
            CharactersTrimmed += (input.Length - trimmed.Length);
        }
        return trimmed;
    }
}

但无论哪种方式,我们是否使用Interlockedlock阻止了不安全的操作,我们已经使方法线程安全。