Optimizing Memory Usage with Streaming

This topic provides recommendations for using streaming patterns to minimize message memory footprints when sending or receiving large messages with a WCF transport or when loading messages in orchestrations.

When using code in an orchestration to read the contents of a message, avoid using XmlDocument variables. Loading a message into an XmlDocument variable incurs significant overhead, especially for large messages. This overhead is in terms of memory usage and processing to build the in-memory structures. The use of an XmlDocument instance forces the entire message contents to be loaded into memory in order to build the object graph for the Document Object Module (DOM). The total amount of memory used by an instance of this class can be around 10 times the actual message size. For more information about the memory footprint required when loading a message into an XmlDocument variable, see Chapter 9 – Improving XML Performance.

The remainder of this topic provides alternative methods for reading message contents that do not require loading a message into an XmlDocument variable.

Use streaming when sending or receiving large messages with a WCF transport

When sending or receiving large messages with a WCF transport, use the WCF-Custom or WCF-CustomIsolated adapter and configure with a binding type that supports the transferMode = Streamed option, such as the following bindings:

  • basicHttpBinding + BasicHttpBindingElement, transferMode = Streamed

  • netTcpBinding + NetTcpBindingElement, transferMode = Streamed

  • customBinding + HttpTransportElement, transferMode = Streamed

  • customBinding +ConnectionOrientedTransportElement, transferMode = Streamed

    Choosing a WCF-Custom or WCF-CustomIsolated adapter along with a binding that supports the transferMode = Streamed option will implement streaming of large messages to the file system as needed, and will mitigate potential out-of-memory issues.

Use streaming to minimize the memory footprint required when loading messages in orchestrations

The following techniques describe how to minimize the memory footprint of a message when loading the message into an orchestration.

Use an XLANGMessage variable to process the contents of a message or message part

When passing a message from an orchestration to .NET class libraries, do not pass them as XmlDocument variables, for reasons mentioned earlier in this topic; use XLANGMessage variables instead. The following techniques illustrate methods for reading a message or message part using an XLANGMessage variable.

  • Process messages with XMLReader - To process a message with an XmlReader instance, pass the message to .NET code as an XLANGMessage, and retrieve the Part content using XmlReader.

    public void ProcessMessage(XLANGMessage message)
    {
        try
        {
            using (XmlReader reader = message[0].RetrieveAs(typeof(XmlReader)) as XmlReader)
            if (reader != null)
            {
                ...
            }
        }
        finally
        {
            message.Dispose();
        }
    }
    
  • Retrieve the contents of a message into a string with StreamReader - One of the common uses of XmlDocument in orchestrations is to retrieve the message as an XML string using XmlDocument.OuterXml(). The following code example illustrates an alternative method which retrieves the message as a string using an XLANGMessage variable.

    public static string MessageToString(XLANGMessage message)
    {
        string strResults;
        try
        {
            using (Stream stream = message[0].RetrieveAs(typeof(Stream)) as Stream)
            {
                using (StreamReader reader = new StreamReader(stream))
                {
                    strResults = reader.ReadToEnd();
                }
            }
        }
        finally
        {
            message.Dispose();
        }
        return strResults;
    }
    
  • Retrieve the contents of simple .NET messages into a string - If the type of the message is a simple .NET type, you can retrieve the message as that type. For example, to get the message as a string, pass the message to the .NET code as an XLANGMessage and retrieve the Part content as a string.

    public void ProcessMessage(XLANGMessage message)
    {
        try
        {
            string content = message[0].RetrieveAs(typeof(string)) as string;
            if (!string.IsNullOrEmpty(content))
            {
                ...
            }
        }
        finally
        {
            message.Dispose();
        }
    }
    
  • Retrieve the contents of a message into a stream - To get the message as a stream, pass the message to the .NET code as an XLANGMessage and retrieve the Part content as a stream.

    public Stream ProcessRequestReturnStream(XLANGMessage message, int bufferSize, int thresholdSize)
    {
       ...
       try
       {
          using (VirtualStream virtualStream = new VirtualStream(bufferSize, thresholdSize))
          {
             using (Stream partStream = (Stream)message[0].RetrieveAs(typeof(Stream)))
             //Note that when calling this code, if the XmlDocument is quite large, keeping it in a memory with a MemoryStream may have an adverse effect on performance.
             //In this case, it may be worthwhile to consider an approach that uses a VirtualStream + ReadonlySeekableStream to buffer it to the file system, if its size is bigger than the thresholdSize parameter.
             //Keep in mind that:
             // - If the message size is smaller than the threshold size, the VirtualStream class buffers the stream to a MemoryStream.
             // - If the message size is bigger than the threshold size, the VirtualStream class buffers the stream to a temporary file.
                using (ReadOnlySeekableStream readOnlySeekableStream = new ReadOnlySeekableStream(partStream, virtualStream, bufferSize))
                {
                   using (XmlReader reader = XmlReader.Create(readOnlySeekableStream))
                   {
    
                   }
                }
             }
          }
       }
       catch (Exception ex)
       {
    
       }
       finally
       {
          message.Dispose();
       }
       return stream;
    }
    
  • Retrieve the contents of a message into a .NET object - To get the message as a .NET object, pass the message to the .NET code as an XLANGMessage and retrieve the Part content as an instance of a .NET class. Create this latter from the Xml Schema of the message using the XML Schema Definition Tool (Xsd.exe) tool provided by Visual Studio 2010.

    Note

    This technique is valid only when messages are small. Otherwise, this approach could incur in a significant overhead to de-serialize the actual message into a .NET object.

    public void ProcessMessage(XLANGMessage message)
    {
        try
          {
          Request request = message[0].RetrieveAs(typeof(Request)) as Request;
          if (request != null)
          {
             ...
          }
       }
       finally
       {
          message.Dispose();
       }
    
    }
    

Note

Use of the Dispose() method exposed by the XLANGMessage parameter before returning from the .NET code is particularly important in looping scenarios and long-running orchestrations that can accumulate instances of the XLANGMessage object without releasing them over time. For more information about calling message.Dispose() in this manner, see Messages Represented as XLANGMessage in the BizTalk Server documentation. This topic also provides best practices for using IStreamFactory to construct XLANGMessage variables in user code using a stream-based approach.

For more information about the different ways to process an XLANGMessage within an helper component invoked by an orchestration, see the following topics:

Using XPathReader and XPathCollection to extract a value from an XLANGMessage object from a method invoked by an orchestration

Avoid using the XMLDocument class to read the contents of XML messages from custom code, such as custom pipeline components or helper classes invoked by orchestrations. When using an XMLDocument instance to load an XML message, the entire message is loaded into memory, which is inefficient and may require memory up to 10 times the actual size of the message. A more efficient way of reading the contents of XML messages is to use a streaming technique to wrap the original stream with one of the stream classes provided by the Microsoft.BizTalk.Streaming.dll assembly. This technique is particularly useful when loading large messages.

If specific values need to be pulled from an XML document, instead of using the SelectNodes and SelectSingleNode methods exposed by the XmlDocument class, use an instance of the XPathReader class provided by the Microsoft.BizTalk.XPathReader.dll assembly as illustrated in the following code example.

  • This example illustrates using the XPathReader and XPathCollection to extract a given value from an XLANGMessage object inside a method invoked by an orchestration.

    public static string SelectSingleNode(XLANGMessage message, string xPath)
    {
        try
        {
            if (message == null || string.IsNullOrEmpty(xPath))
            {
                return string.Empty;
            }
            using (XmlReader reader = (XmlReader)message[0].RetrieveAs(typeof(XmlReader)))
            {
                XPathCollection xPathCollection = new XPathCollection();
                XPathReader xPathReader = new XPathReader(reader, xPathCollection);
                xPathCollection.Add(xPath);
                while (xPathReader.ReadUntilMatch())
                {
                    if (xPathReader.Match(0))
                    {
                        return xPathReader.ReadString();
                    }
                }
            }
        }
        catch (Exception ex)
        {
            ...
        }
        finally
        {
            message.Dispose();
        }
        return string.Empty;
    }
    

See Also

Optimizing BizTalk Server Applications