Partager via


XmlSchemaSet Thread Safety

Here's a good word of warning: even if an object "feels" read-only because you're not calling code to modify it, if it's not documented as safe for use from multiple threads, then you shouldn't risk it.

As an example, let’s look at XmlSchema and XmlSchemaSet. Initializing these has a cost associated with it, and so it's nice to be able to build them once and then reuse them. But you have to be very careful in doing this. The docs say that all instance methods are not safe for multiple thread usage, but you don't really use them directly during validation, so it's hard to tell from the outside what's safe and what's not.

In a nutshell, the only thing you can do that is safe for concurrent usage is to use a validating reader. Here's the sample code to try this out (for some reason, this "breaks" more on 64-bit machines, but it's unsafe on all architectures).

First, a little helper to create an XmlSchema.

private XmlSchema CreateSchema()
{
string schemaText = @"<?xml version='1.0'?>
<xs:schema id='play' targetNamespace='https://tempuri.org/play.xsd'
elementFormDefault='qualified' xmlns='https://tempuri.org/play.xsd'
xmlns:xs='https://www.w3.org/2001/XMLSchema'>
<xs:element name='myShoeSize'>
<xs:complexType>
<xs:simpleContent>
<xs:extension base='xs:decimal'>
<xs:attribute name='sizing' type='xs:string' />
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:schema>";

using (StringReader reader = new StringReader(schemaText))
{
return XmlSchema.Read(reader, null);
}
}

Next, a simple XmlSchemaSet.

private XmlSchemaSet CreateSchemaSet(XmlSchema schema)
{
XmlSchemaSet set = new XmlSchemaSet();
set.Add(schema);
set.Compile();
return set;
}

Finally, some validation:

private void ValidateDocument(XmlSchemaSet set)
{
string doc = @"<myShoeSize xmlns='https://tempuri.org/play.xsd' sizing='123' />";
XmlReaderSettings settings = new XmlReaderSettings();
settings.Schemas = set;
settings.ValidationEventHandler += new ValidationEventHandler(settings_ValidationEventHandler);
using (StringReader reader = new StringReader(doc))
using (XmlReader x = XmlReader.Create(reader, settings))
{
while (x.Read()) { }
}
}

private int failCount;
void settings_ValidationEventHandler(object sender, ValidationEventArgs e)
{
System.Threading.Interlocked.Increment(ref failCount);
}

Now, armed with these, I will show you some code that is thread-safe, but that a single line reorder would cause to break.

XmlSchema schema = CreateSchema();
Thread[] threads = new Thread[10];
XmlSchemaSet set = CreateSchemaSet(schema);
for (int i = 0; i < threads.Length; i++)
{
threads[i] = new Thread((x) =>
{
for (int j = 0; j < 1000; j++)
{
// If the CreateSchemaSet were here
// instead of outside this would break!
//
// Don't add the schema to the
// XmlSchemaSet from multiple threads!
//
// XmlSchemaSet set = CreateSchemaSet(schema);
//
ValidateDocument(set);
}
});
}

Array.ForEach(threads, (t) => t.Start());
Array.ForEach(threads, (t) => t.Join());
this.Text = "Failure count: " + failCount; 

The part before the thread creation runs on a single thread, and so there are no multi-thread concerns; the stuff inside the callback is happening on multiple threads at the same time. You can only use the set for validation here!

Enjoy!

Marcelo Lopez Ruiz

https://blogs.msdn.com/marcelolr/

Comments

  • Anonymous
    April 27, 2009
    PingBack from http://microsoft-sharepoint.simplynetdev.com/xmlschemaset-thread-safety/

  • Anonymous
    April 29, 2009
    Do we get to know why they break inside?

  • Anonymous
    May 08, 2009
    All Marcelo saying that is calls to settings_ValidationEventHandler are not thread safe, if you access the objects without locks you might have unexpected results.