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

9

我有一个程序,它从数据库中读取以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次。它们似乎与任何特定类型的消息都无关,完全是随机的。

有什么想法是什么导致这些错误以及如何解决?

预计 - 如果有帮助,这是导致错误的代码:

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个回答

9

一些网站指出,使用病毒扫描工具时可能会出现CVTRES.EXE错误。 - user7116
1
+1同意 - 我认为你提到的病毒扫描器的想法很有道理。至于预先创建序列化程序,那也是一个不错的选择,尽管对于我正在处理的特定应用程序来说可能会变得有些复杂。 - Eric Petroelje
禁用我的病毒扫描器对我有用。当启用AVG互联网安全时,XmlSerializer构造函数从未返回。 - Surfbutler

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个构造函数更严重。
以上全部都是猜测,但如果是我的话,我肯定会确认不是这个问题。

一个好的观点,但是这个应用程序不是多线程的,所以在我的情况下这不应该是一个问题。 - Eric Petroelje
@Eric 所以它不是一个 Web 服务或类似的东西吗?是一个客户端程序通过正在处理的消息日志,逐个完成的过程吗? - eglasius
这是一个命令行应用程序,作为计划任务运行,并逐条处理数据库表中“队列”中的记录。 - Eric Petroelje
@Eric,如果同时发生两个预定的激活,会怎么样?/ 不应该导致问题,只是想知道。无论如何,我的答案中的解决方案在那种情况下不起作用,我肯定不希望这样的事情引起问题。但这是一个奇怪的错误,所以我不会排除任何可能性而不进行检查。 - eglasius
不应该发生这种情况,因为任务设置为如果由于某种原因已经在运行,则不运行。无论如何,那将是一个单独的进程,所以正如你所提到的,它不应该干扰。 - Eric Petroelje

1
对于第一个错误(无法执行程序),您可能遇到了我们遇到的相同的 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实例时,异常就会被抛出。我添加了跟踪语句来显示GetCurrentDirectory()的结果,确实设置为WillBeDeleted文件夹。修复方法是在应用程序初始化期间将SetCurrentDirectory设置为有效位置,在进行任何序列化之前。

0

这表明您没有缓存序列化程序,这是非常不好的 => 它会导致内存泄漏,我怀疑您将会遇到这个问题。

请记住,.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));
                }
            }
        }
    }

}

一个好的建议,我会考虑的,但是由于这个程序的设置方式,它可能并不重要。基本上,它每隔几分钟作为一个任务运行,处理一些消息,然后退出。因此,缓存序列化器的效益非常有限。 - Eric Petroelje
如果您在同一运行期间多次创建它们,那么很可能会出现这种情况。 - Aliostad
根据这个问题,.NET框架在内部缓存序列化程序(我没有使用任何XmlAttributeOverrides)。这不是实际情况吗?https://dev59.com/30bRa4cB1Zd3GeqP5fyN - Eric Petroelje
所以我按照你建议的实现了序列化器缓存,但我仍然遇到了相同的错误。 - Eric Petroelje

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接