WCF的DataContractSerilaizer线程安全吗?

时间:2012-10-23 03:52:37

标签: c# .net multithreading wcf thread-safety

我已经将一个相当大的系统从Remoting转换为WCF,并且一切似乎运行良好,除了我们经常得到以下异常:" System.InvalidOperationException:Collection被修改;枚举操作可能无法执行。"我没有运气跟踪它,因为只有在有数百个电话通过时才会发生,我只能假设它是因为一个对象被修改,因为它被序列化了。

所有类都使用:[DataContract(IsReference=true)]

使用远程处理时没有类似的例外情况,所以我想知道是否有人在WCF中遇到类似的问题,或者可以让我知道它可能是序列化程序 - 在这种情况下我假设我必须写我自己的序列化程序在必要时执行locks(这是我最不愿意避免的一项重大任务)。

以下是堆栈跟踪:

WCF Error: at System.Collections.Generic.List1.Enumerator.MoveNextRare() at WriteArrayOfLineToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , CollectionDataContract ) at System.Runtime.Serialization.CollectionDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context) at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerialize(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle) at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerializeReference(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle) at WriteLineGroupToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , ClassDataContract ) at System.Runtime.Serialization.ClassDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context) at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle) at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerialize(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle) at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerializeReference(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle) at WriteLineToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , ClassDataContract ) at System.Runtime.Serialization.ClassDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context) at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeAndVerifyType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, Boolean verifyKnownType, RuntimeTypeHandle declaredTypeHandle, Type declaredType) at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithXsiTypeAtTopLevel(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle originalDeclaredTypeHandle, Type graphType) at System.Runtime.Serialization.DataContractSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) at System.Runtime.Serialization.XmlObjectSerializer.WriteObject(XmlDictionaryWriter writer, Object graph) at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.SerializeParameterPart(XmlDictionaryWriter writer, PartInfo part, Object graph) at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.SerializeParameter(XmlDictionaryWriter writer, PartInfo part, Object graph) at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.SerializeBody(XmlDictionaryWriter writer, MessageVersion version, String action, MessageDescription messageDescription, Object returnValue, Object[] parameters, Boolean isRequest) at System.ServiceModel.Dispatcher.OperationFormatter.SerializeBodyContents(XmlDictionaryWriter writer, MessageVersion version, Object[] parameters, Object returnValue, Boolean isRequest) at System.ServiceModel.Dispatcher.OperationFormatter.OperationFormatterMessage.OperationFormatterBodyWriter.OnWriteBodyContents(XmlDictionaryWriter writer) at System.ServiceModel.Channels.BodyWriter.WriteBodyContents(XmlDictionaryWriter writer) at System.ServiceModel.Channels.BodyWriterMessage.OnWriteBodyContents(XmlDictionaryWriter writer) at System.ServiceModel.Channels.Message.OnWriteMessage(XmlDictionaryWriter writer) at System.ServiceModel.Channels.Message.WriteMessage(XmlDictionaryWriter writer) at System.ServiceModel.Channels.BufferedMessageWriter.WriteMessage(Message message, BufferManager bufferManager, Int32 initialOffset, Int32 maxSizeQuota) at System.ServiceModel.Channels.BinaryMessageEncoderFactory.BinaryMessageEncoder.WriteMessage(Message message, Int32 maxMessageSize, BufferManager bufferManager, Int32 messageOffset) at System.ServiceModel.Channels.FramingDuplexSessionChannel.EncodeMessage(Message message) at System.ServiceModel.Channels.FramingDuplexSessionChannel.OnSend(Message message, TimeSpan timeout) at System.ServiceModel.Channels.OutputChannel.Send(Message message, TimeSpan timeout) at System.ServiceModel.Dispatcher.DuplexChannelBinder.DuplexRequestContext.OnReply(Message message, TimeSpan timeout) at System.ServiceModel.Channels.RequestContextBase.Reply(Message message, TimeSpan timeout) at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.Reply(MessageRpc& rpc)

2 个答案:

答案 0 :(得分:5)

实际上,可以使用DataContractSerializer轻松复制此错误。它没有引用DataContractSerializer的线程安全性,它是关于某些集合的线程安全性,用于您的数据协定:

[DataContract]
public class C
{
    [DataMember]
    public List<int> Values { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var c = new C
        {
            Values = new List<int>()
        };

        var serializer = new DataContractSerializer(typeof(C));

        Task
            .Factory
            .StartNew(() => 
            {
                while (true)
                {
                    Console.WriteLine("Trying to add new item.");
                    c.Values.Add(DateTime.Now.Millisecond);
                }
            }, 
            TaskCreationOptions.LongRunning);

        Task
            .Factory
            .StartNew(() =>
            {
                while (true)
                {
                    using (var stream = new MemoryStream())
                    {
                        Console.WriteLine("Trying to serialize.");
                        serializer.WriteObject(stream, c);
                    }
                }
            },
            TaskCreationOptions.LongRunning);

        Console.ReadLine();
    }

执行时间过短后,您将获得具有类似堆栈跟踪的IOE:

   at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)    at System.Collections.Generic.List`1.Enumerator.MoveNextRare()    at System.Collections.Generic.List`1.Enumerator.MoveNext()    at WriteArrayOfintToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , CollectionDataContract )    at System.Runtime.Serialization.CollectionDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)

看起来你正在继续修改一些同时被序列化的共享数据。您可以打开WCF跟踪(请参阅this问题)以找出导致此错误的操作,并仔细查看此操作使用的数据。

然后,根据当前服务行为的InstanceContextModeConcurrencyMode属性的值,您可以选择要走的路:

  • 要么使用一些锁定;
  • 或使用任何线程安全的集合;
  • 或改变服务行为;
  • 或改变服务本身(例如,使其成为无国籍)。

答案 1 :(得分:1)

如果丹尼斯的假设是正确的,那么解决这个问题的最简单方法是复制集合并通过网络发送副本。此时,在序列化期间是否修改了原始文件无关紧要