2012-10-23 70 views
9

我一直在將一個相當大的系統從Remoting轉換爲WCF,並且一切似乎都運行良好,除了我們經常得到以下異常:「System.InvalidOperationException:集合已被修改;枚舉操作可能無法執行。我沒有任何運氣追蹤它,因爲它只發生在有數百個呼叫通過時發生,我只能假設它是因爲一個對象正在被序列化時被修改。WCF的DataContractSerilaizer線程安全嗎?

這些類全部使用:[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)

+5

對於它的價值,錯誤「System.InvalidOperationException:Collection被修改;枚舉操作可能無法執行」。很少有與線程有關的事情。一般來說,這是因爲這個集合在被列舉的過程中已經被修改過了,這與'foreach(列表中的var item)列表一樣難以複製。移除(item);'。 –

+0

你的枚舉是什麼?如果我的枚舉正在使用在我完成序列化之前處理的資源,那麼我遇到了同樣的錯誤。 – twreid

+1

@Kirk,如果是這樣的話,堆棧跟蹤不會提示它在串行器中完成,因此是WCF中的一個錯誤?我相對確定我們的代碼不會那麼做,因爲我過去自己做過很多次,所以我(終於)知道要注意它 – user1766568

回答

4

事實上,這種錯誤可以很容易地與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問題)以瞭解哪種操作會導致此錯誤,並仔細查看該操作正在使用哪些數據。

然後,取決於InstanceContextMode值和當前服務行爲的ConcurrencyMode屬性,你可以選擇哪個方向走:

  • 或者使用一些鎖定;
  • 或使用任何線程安全的集合;
  • 或改變服務行爲;
  • 或改變服務本身(例如,使其無狀態)。
+0

感謝您的示例和詳細的答案。大多數服務都是InstanceContextMode.PerCall和ConcurrencyMode.Mulitple,所以我可能會使用一個線程安全的集合(我不知道),這取決於WCF跟蹤顯示的是什麼 - 在我的困惑中,我完全忘記了跟蹤雖然沒有你的闡述,但仍然不知道最好的解決方案。謝謝! – user1766568

+0

@ user1766568:我希望你不會把每個集合都換成線程安全模擬,因爲這是一種下地獄。 – Dennis

1

如果丹尼斯的假設是正確的,那麼解決這個問題的最簡單方法是複製集合並通過網絡發送副本。在這一點上,原始序列化過程中是否被修改並不重要

+0

感謝您的回覆。可能是最簡單的,但對象圖可能非常深,所以我不確定它會是這個特例中最快的。我會返回一個可能有一個集合的對象,或者有一個包含集合的對象集合等等。所以認爲我可能不得不使用線程安全的集合。 – user1766568

+0

這不是一個假設。 :)您可以編譯代碼示例以確保它。 – Dennis

+0

我不是說你的代碼沒有證明這個問題 - 我說的只是因爲你的代碼有相同的症狀並不意味着它和OP一樣。但是,如果你的代碼是同一個原因,那麼複製是解決問題的一種乾淨方式 –