从XML反序列化对象时出现间歇性错误

时间:2011-01-25 13:20:33

标签: c# .net .net-3.5 xml-serialization

我有一个程序,它将存储为XML的对象作为数据库(基本上是一个消息队列)并对它们进行反序列化。间歇性地,我将得到以下错误之一:

System.Runtime.InteropServices.ExternalException: Cannot execute a program. The command being executed was "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\csc.exe" /noconfig /fullpaths @"C:\Documents and Settings\useraccount\Local Settings\Temp\lh21vp3m.cmdline".
   at System.CodeDom.Compiler.Executor.ExecWaitWithCaptureUnimpersonated(SafeUserTokenHandle userToken, String cmd, String currentDir, TempFileCollection tempFiles, String& outputName, String& errorName, String trueCmdLine)
   at System.CodeDom.Compiler.Executor.ExecWaitWithCapture(SafeUserTokenHandle userToken, String cmd, String currentDir, TempFileCollection tempFiles, String& outputName, String& errorName, String trueCmdLine)
   at Microsoft.CSharp.CSharpCodeGenerator.Compile(CompilerParameters options, String compilerDirectory, String compilerExe, String arguments, String& outputFile, Int32& nativeReturnValue, String trueArgs)
   at Microsoft.CSharp.CSharpCodeGenerator.FromFileBatch(CompilerParameters options, String[] fileNames)
   at Microsoft.CSharp.CSharpCodeGenerator.FromSourceBatch(CompilerParameters options, String[] sources)
   at Microsoft.CSharp.CSharpCodeGenerator.System.CodeDom.Compiler.ICodeCompiler.CompileAssemblyFromSourceBatch(CompilerParameters options, String[] sources)
   at System.CodeDom.Compiler.CodeDomProvider.CompileAssemblyFromSource(CompilerParameters options, String[] sources)
   at System.Xml.Serialization.Compiler.Compile(Assembly parent, String ns, XmlSerializerCompilerParameters xmlParameters, Evidence evidence)
   at System.Xml.Serialization.TempAssembly.GenerateAssembly(XmlMapping[] xmlMappings, Type[] types, String defaultNamespace, Evidence evidence, XmlSerializerCompilerParameters parameters, Assembly assembly, Hashtable assemblies)
   at System.Xml.Serialization.TempAssembly..ctor(XmlMapping[] xmlMappings, Type[] types, String defaultNamespace, String location, Evidence evidence)
   at System.Xml.Serialization.XmlSerializer.GenerateTempAssembly(XmlMapping xmlMapping, Type type, String defaultNamespace)
   at System.Xml.Serialization.XmlSerializer..ctor(Type type, String defaultNamespace)
   at System.Xml.Serialization.XmlSerializer..ctor(Type type)
   .....

或者我会得到这个:

System.InvalidOperationException: Unable to generate a temporary class (result=1).
error CS0016: Could not write to output file 'c:\Documents and Settings\useraccount\Local Settings\Temp\nciktsd7.dll' -- 'Could not execute CVTRES.EXE.'

   at System.Xml.Serialization.Compiler.Compile(Assembly parent, String ns, XmlSerializerCompilerParameters xmlParameters, Evidence evidence)
   at System.Xml.Serialization.TempAssembly.GenerateAssembly(XmlMapping[] xmlMappings, Type[] types, String defaultNamespace, Evidence evidence, XmlSerializerCompilerParameters parameters, Assembly assembly, Hashtable assemblies)
   at System.Xml.Serialization.TempAssembly..ctor(XmlMapping[] xmlMappings, Type[] types, String defaultNamespace, String location, Evidence evidence)
   at System.Xml.Serialization.XmlSerializer.GenerateTempAssembly(XmlMapping xmlMapping, Type type, String defaultNamespace)
   at System.Xml.Serialization.XmlSerializer..ctor(Type type, String defaultNamespace)
   at System.Xml.Serialization.XmlSerializer..ctor(Type type)
   ....

该程序每天成功处理数千条消息,但我每天只能获得2到3次这些错误。它们似乎与任何特定类型的消息无关,只是完全随机的。

知道造成这些错误的原因以及解决方法吗?

ETA - 以下是导致错误的代码,如果有帮助:

public class MessageContextBuilder<T> where T : MessageContextBase 
{
    private static IDictionary<string, XmlSerializer> SerializerCache { get; set; }
    public ILog Logger { get; set; }


    public MessageContextBuilder() {
        if (SerializerCache == null) SerializerCache = new Dictionary<string, XmlSerializer>();
        Logger = LogContextManager.Context.GetLogger<MessageContextBuilder<T>>();
    }

    public T BuildContextFromMessage(IEmailQueueMessage msg) {
        XmlSerializer serializer = GetSerializer(typeof(T));
        XmlReader r = XmlReader.Create(new StringReader(msg.MessageDetails));
        if (serializer.CanDeserialize(r)) {
            T rval = (T)serializer.Deserialize(r);
            rval.EmailAddress = msg.EmailAddress;
            rval.LocaleID = msg.LocaleID;
            rval.StoreID = msg.StoreID;
            rval.MessageID = msg.UniqueKey;
            return rval;
        } else {
            throw new ArgumentException("Cannot deserialize XML in message details for message #" + msg.UniqueKey);
        }
    }

    public XmlSerializer GetSerializer(Type t) {
        if (!SerializerCache.ContainsKey(t.FullName)) {
            SerializerCache.Add(t.FullName, new XmlSerializer(t)); // Error occurs here, in XmlSerializer constructor, intermittently
        }
        return SerializerCache[t.FullName];
    }
}

4 个答案:

答案 0 :(得分:9)

您可以预先创建序列化程序:http://msdn.microsoft.com/en-us/library/bk3w6240%28v=VS.100%29.aspx只需试一试即可。下一个针对此类问题的规范候选者是您的病毒扫描程序。您的工具在创建序列化程序时正在写入光盘。我见过病毒扫描程序会在这种情况下产生各种奇怪的错误。

答案 1 :(得分:1)

XmlSerializer 假设是线程安全的。

即使是这种情况,你也可以注意到你在两种情况下都失败的行为:XmlSerializer..ctor(Type type)

鉴于此,它看起来像是一个尝试创建序列化程序的多线程限制。

我建议您使用以下代码:

public XmlSerializer GetSerializer(Type t) {
        if (!SerializerCache.ContainsKey(t.FullName)) {
            SerializerCache.Add(t.FullName, new XmlSerializer(t)); // Error occurs here, intermittently
        }
        return SerializerCache[t.FullName];
    }

并在Add上实现锁定。这样,您一次只能创建一个序列化程序。如果你没有处理大量不同类型,那么打击很小。

请注意,无论如何您都需要锁定,因为当您尝试同时添加两种类型时,您可能会遇到重复的异常。

static object serializerCacheLock = new object();
public XmlSerializer GetSerializer(Type t) {
        if (!SerializerCache.ContainsKey(t.FullName))
        lock(serializerCacheLock)
        if (!SerializerCache.ContainsKey(t.FullName)) {
            SerializerCache.Add(t.FullName, new XmlSerializer(t));
        }
        return SerializerCache[t.FullName];
    }

如果仍然不够,我会尝试对序列化器构造函数与序列化程序的使用进行读/写锁定。可能是多线程问题的思路比同时运行的2个ctors值得多。

以上都是一个巨大的猜测,但如果是我,我肯定会证实不是那样。

答案 2 :(得分:0)

这表示您没有缓存完全不擅长的序列化程序=&gt;它导致内存泄漏,我怀疑你会遇到这种情况。

请记住,.NET会生成代码并将它们编译成程序集每次创建一个序列化程序。

始终创建序列化程序,然后缓存它们。

以下是一个示例:

public class SerialiserCache
{

    private static readonly SerialiserCache _current = new SerialiserCache();
    private Dictionary<Type, XmlSerializer> _cache = new Dictionary<Type, XmlSerializer>();

    private SerialiserCache()
    {

    }

    public static SerialiserCache Current
    {
        get { return _current; }
    }

    public XmlSerializer this[Type t]
    {
        get
        {
            LoadIfNecessary(t);
            return _cache[t];
        }
    }

    private void LoadIfNecessary(Type t)
    {

        // double if to prevent race conditions 
        if (!_cache.ContainsKey(t))
        {
            lock (_cache)
            {
                if (!_cache.ContainsKey(t))
                {
                    _cache[t] = new XmlSerializer(typeof(T));
                }
            }
        }
    }

}

答案 3 :(得分:0)

对于第一个错误(无法执行程序),您可能遇到了我们遇到的同一个XmlSerializer bug。事实证明,当Directory.CurrentDirectory设置为不再存在的文件夹时,XmlSerlializer会抛出该异常。

我们的具体情况与您的情况不同,但我会详细说明,以便它可以帮助您了解可能发生的事情,或者它可以帮助其他任何人。在我们的案例中,我们的少数客户在直接从安装程序启动WinForms应用程序后会收到该错误,即他们在安装或升级后选择“立即运行”选项。 (不清楚为什么它发生在某些人而不是其他人身上)。我们怀疑发生的事情是我们的安装程序(InstallAware)偶尔启动我们的应用程序,当前目录设置为不再存在或即将被删除的文件夹。为了测试这个理论,我编写了一个模拟从安装程序启动的测试应用程序:

    string dir = @"C:\Users\me\Documents\Temp\WillBeDeleted";
    Directory.CreateDirectory(dir);
    Directory.SetCurrentDirectory(dir);

    Process.Start(@"C:\Program Files (x86)\...\our.exe");

    Directory.SetCurrentDirectory(@"C:\");  // otherwise, won't be able to delete
    Directory.Delete(dir);

果然,只要启动的应用程序创建了一个新的XmlSerializer实例,就会抛出异常。我放入trace语句来显示GetCurrentDirectory()的结果,实际上它被设置为 WillBeDeleted 文件夹。在任何序列化发生之前,修复程序是在应用程序初始化期间将SetCurrentDirectory置于有效位置。