Multi-Threading, Silverlight Sockets & Visualisation Part 3
Part 2 of this Silverlight visualisation instalment discussed how data could be streamed over a socket for rendering on the client.
To recap, Part 1 discussed how the Training Set data was loaded from file and stored in memory. Well the data structure is as follows:
private void SetImage(Stream stream) { BitmapImage bi = new BitmapImage(); bi.SetSource(stream); this.Image1.Source = null; this.Image1.Source = bi; } [XmlType("T")] public struct TrianglePoints { public int P1 { get; set; } public int P2 { get; set; } public int P3 { get; set; } } [XmlType("P")] public struct TrianglePoint { public float X { get; set; } public float Y { get; set; } } /// <summary> /// A container for all the data that needs to be sent over the wire to render the image /// </summary> [XmlType("D")] public class TrainingSetData { public int Index { get; set; } public TrianglePoint[] Points { get; set; } public TrianglePoints[] Triangles { get; set; } public int Height { get; set; } public int Width { get; set; } [XmlIgnore] public byte[] Image { get; set; } public string ImageUrl { get; set; } public string ImageContentType { get; set; } }
Notice these classes have been attributed so that can be XML serialized, but that the Image blob is ignored given it is sent over as a separate block. In addition the Socket Server that broadcasts the training set data has a configuration flag to disable streaming the image at all. In which case the client can download the image via the specified Url.
Silverlight supports loading an image in a number of ways
1. Relative or Absoulte Uri’s.
2. Set directly via resource stream (loaded from memory, file etc).
3. Downloading on demand using the asynchronous web client.
Option 1 is not appropriate given my images are dynamic and change on each request, so I have implemented Option 2 (when image is streamed over socket), and Option3 (when downloading) as the following code illustrates:
private void RenderImage(TrainingSetData data, byte[] image) { try { if (image == null || image.Length == 0) { // Fire off a request to the web service to download the image if (!String.IsNullOrEmpty(data.ImageUrl)) { WebClient wc = new WebClient(); wc.OpenReadCompleted += new OpenReadCompletedEventHandler(Image_OpenReadCompleted); wc.OpenReadAsync(new Uri(data.ImageUrl, UriKind.Absolute), data.Index); System.Diagnostics.Debug.WriteLine("Image Requested"); } else { System.Diagnostics.Debug.WriteLine("Empty Image Url"); } } else { // Load the image setting the content type, so it knows how to read the file using (MemoryStream ms = new MemoryStream(image)) { StreamResourceInfo resource = new StreamResourceInfo(ms, null); SetImage(resource.Stream); } System.Diagnostics.Debug.WriteLine("Image Streamed"); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("Error rendering image: " + ex); } } void Image_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e) { if (e.Error == null) { try { StreamResourceInfo sri = new StreamResourceInfo(e.Result as Stream, null); SetImage(sri.Stream); System.Diagnostics.Debug.WriteLine("Image downloaded"); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("Error downloading image: " + ex); } } }
The ImageUrl in this case is served up by the WCF service as configured by the Socket Service:
<appSettings>
<add key="ModelFilename" value="..\..\..\..\data\face\models\tim_face.smd" />
<add key="Loader.VisualisationServiceUri" value="http://localhost:1236/VisualisationService.svc" />
</appSettings>
This WCF service uses the .NET 3.5 extensions to serve up the images in a RESTful manner
[ServiceContract] public interface IVisualisationService { [OperationContract] [WebGet(UriTemplate = "image/{index}")] Stream GetImage(string index); }
And the following implementation:
public class VisualisationService : IVisualisationService { /// <summary> /// Gets the image. /// </summary> /// <param name="index">The index.</param> /// <returns></returns> public Stream GetImage(string index) { // Pull the model filename from config string modelFilename = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ConfigurationManager.AppSettings["ModelFilename"])); int i = Int32.Parse(index); // Load the training data VisualisationLoader loader = new VisualisationLoader(); TrainingSetData d = loader.Read(modelFilename)[i]; MemoryStream ms = new MemoryStream(d.Image, false); // Set the content type and return stream WebOperationContext.Current.OutgoingResponse.ContentType = d.ImageContentType; return ms; } }
After applying the appropriate webHttpBinding configuration, the only other consider is to include a crossdomain.xml file in the web server root, otherwise Silverlight will through a protocol error.
<?xml version="1.0"?>
<!–
http://www.mysite.com/crossdomain.xml –>
<cross-domain-policy>
<allow-access-from domain="*" />
</cross-domain-policy>
It is also worth pointing out that you must be running under a web project to test download of Images. See this blog post for this and more helpful tips.
From my testing send the image stream directly over the socket resulted in better performance, but the delay in ‘downloading’ the image once the process fired up was not significant.
Multi-Threading, Silverlight Sockets & Visualisation Part 2
Having successfully loaded my Visualisation data in Part 1, this article will demonstrate how to send binary encoded data over a socket to a Silverlight application.
Silverlight 2 has supports for asynchronous sockets, which can list to a data-stream connected to servers on a port between 4502 and 4532. See Dan Wahlin’s good article on setting up a server for sending text messages.
My sample required sending a large amount of data the Model points + triangles, as well as the raw image stream. This proved to be a challenge with sockets, as there is no well defined beginning or end point of a message, and this volume of data doesn’t can result in an almost constant stream of information.
The solution was to define a magic byte that what identify the start of the ‘message’, which was immediately followed by the size of the Body (as XML meta-data) and the image as binary stream:
Magic(1) |
BodySize |
ImageSize |
Body |
Image |
Magic(2) |
A BinaryWriter was used to send this formatted data to all listening clients:
private const UInt32 Magic = 0xCAB2FAC; private void SendData(string data, byte[] image) { if (_clientStreams != null) { lock (_clientStreams) { List<BinaryWriter> deadWriters = new List<BinaryWriter>(); // Write the data foreach (BinaryWriter writer in _clientStreams) { if (writer != null) { try { // Get the body byte[] body = Encoding.UTF8.GetBytes(data); // Get bytes together List<byte> bytes = new List<byte>(); bytes.AddRange(BitConverter.GetBytes(Magic)); bytes.AddRange(BitConverter.GetBytes((UInt32)body.Length)); bytes.AddRange(BitConverter.GetBytes((UInt32)image.Length)); bytes.AddRange(body); bytes.AddRange(image); // Write the out writer.Write(bytes.ToArray()); } catch (Exception) { // TODO: Trap specific connection exception deadWriters.Add(writer); } } } // Remove the dead writers foreach (BinaryWriter writer in deadWriters) { writer.Close(); _clientStreams.Remove(writer); } } } }
Silver 2 provides an asynchronous socket implementation which once connected can be wired up to receive events when data is transferred:
void Page_Loaded(object sender, RoutedEventArgs e) { try { string host = Application.Current.Host.Source.DnsSafeHost; int ip = 4530; DnsEndPoint endPoint = new DnsEndPoint(host, ip); Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); reader = new VisualisationReader(); reader.Complete += new EventHandler<VisualisationReadEventArgs>(reader_Complete); SocketAsyncEventArgs args = new SocketAsyncEventArgs(); args.UserToken = socket; args.RemoteEndPoint = endPoint; args.Completed += new EventHandler<SocketAsyncEventArgs>(OnSocketConnectCompleted); socket.ConnectAsync(args); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("Load Exception " + ex); } } private void OnSocketConnectCompleted(object sender, SocketAsyncEventArgs e) { // Read initial data byte[] buffer = new byte[1024]; e.SetBuffer(buffer, 0, buffer.Length); if (e.SocketError == SocketError.Success) { e.Completed -= new EventHandler<SocketAsyncEventArgs>(OnSocketConnectCompleted); e.Completed += new EventHandler<SocketAsyncEventArgs>(OnSocketReceive); ReadMoreData(e); } } private void OnSocketReceive(object sender, SocketAsyncEventArgs e) { try { if (e.BytesTransferred > 0) { reader.Read(e); } // Read More ReadMoreData(e); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("Exception " + ex); } } void ReadMoreData(SocketAsyncEventArgs e) { Socket socket = (Socket)e.UserToken; if (!socket.ReceiveAsync(e)) { // Check this for stack overflow OnSocketReceive(socket, e); } } void reader_Complete(object sender, VisualisationReadEventArgs e) { try { // Get the data set TrainingSetData d = GetData(e.Body); // Invoke Action<TrainingSetData, byte[]> handler = RenderData; this.Dispatcher.BeginInvoke(handler, new object[] { d, e.Image }); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("Error reading body: " + e.Body); System.Diagnostics.Debug.WriteLine(ex); throw; } }
My sample wraps up the logic of interpreting the binary stream into a visualisation reader which calls back to the client once a message has been received.
/// <summary> /// Reads the socket data stream /// </summary> public class VisualisationReader { /// <summary> /// Token to track the progress of reading the socket data /// </summary> protected enum Token { None, Header, Body, Image, Complete } /// <summary> /// Header struct to contain the structure of the message /// </summary> protected struct Header { public Header(uint body, uint pixels) { BodyLength = (int)body; ImagePixels = (int)pixels; } public int BodyLength; public int ImagePixels; } public event EventHandler<VisualisationReadEventArgs> Complete; public void Read(SocketAsyncEventArgs e) { e.SetBuffer(buffer, 0, buffer.Length); transfered = e.BytesTransferred; System.Diagnostics.Debug.WriteLine("Reading... {0}", transfered); // Reset the number of bytes read read = 0; while (read < transfered) { Token token = NextToken(); if (token == Token.Complete) { OnComplete(new VisualisationReadEventArgs(body.ToString(), image)); ResetToken(); System.Diagnostics.Debug.WriteLine("Break..."); break; // Break out of this read } else if (token == Token.None) { // Exit if we didn't match anything break; } } } /// <summary> /// Raises the SomeEvent event /// </summary> protected virtual void OnComplete(VisualisationReadEventArgs e) { EventHandler<VisualisationReadEventArgs> handler; lock (this) { handler = Complete; } if (handler != null) { handler(this, e); } } protected Token NextToken() { if (currentToken == Token.None) { // Wait until i get a magic byte at the begining of the buffer if (BitConverter.ToUInt32(buffer, 0) == Magic) { currentToken = Token.Header; System.Diagnostics.Debug.WriteLine("Read Magic"); read = 4; } } else if (currentToken == Token.Header) { // Build the header for body + image size header = new Header(BitConverter.ToUInt32(buffer, 4), BitConverter.ToUInt32(buffer, 8)); read += 8; // Initialize for body currentToken = Token.Body; readTo = read + header.BodyLength; body = new StringBuilder(header.BodyLength); System.Diagnostics.Debug.WriteLine("Read Header"); } else { // ... Implementation Detail for Body and Image ... } } private void ResetToken() { currentToken = Token.None; } }
That complete call back contains all the necessary information for rendering the image and overlaying the data points on the sliverlight canvas. Part 3 will cover this in more detail including an alternative implementation for downloading an image on demand over a RESTful WCF web service.
The source is avaialble for download.
Multi-Threading, Silverlight Sockets & Visualisation Part 1
Before getting to the visualisation however, I will cover off loading the dataset, and the multi-threading performance tests I did along the way.
The face dataset consists of three directories
· images
· points
· models
A model is defined by a series of images, each of which map to a points file which contain a fixed array of X,Y co-ordinates. These points map to triangles that form the polygons that are drawn on top of the image for each Timestep in the dataset.
Model contains the training set:
training_set:
{
107_0764.pts : 107_0764.jpg
107_0766.pts : 107_0766.jpg
…
107_0791.pts : 107_0791.jpg
107_0792.pts : 107_0792.jpg
}
Points for for each time step contains 68 points at slightly different co-ordinates.
version: 1
n_points: 68
{
249.599 153.934
246.122 205.482
…
384.186 323.087
394.303 235.871
}
The triangles files indicates which points are joined to form the pologyons.
version: 1
n_triangles: 113
{
{ v1: 0 v2: 21 v3: 27 }
{ v1: 27 v2: 0 v3: 1 }
…
{ v1: 58 v2: 6 v3: 5 }
{ v1: 61 v2: 58 v3: 60 }
}
My sample contains a Loader responsible for parsing these files as and putting the object model into an in-memory cache. Given the training data set contains a sequential list of files; loading of this information is inherently parallelisable.
When dealing with multi-threading scenarios, resource locking also becomes an issue, so I constructed a unit test which compares the following thread models:
1. Single Threaded
2. Pooled: Multi-Threaded using ThreadPool
3. Parallel: Multi-Threaded using Microsoft’s new Task Parallel Library (TPL)
With the following locking models
· Read/Write Lock
· Write Lock
Following is the sample code for the Pooled method
private void RunPooled(Action threadProc) { // Set the stop date stop = DateTime.Now.AddSeconds(runFor); // Use an event to wait for all work items to complete int count = threadCount; using (var mre = new ManualResetEvent(false)) { for (int i = 0; i < threadCount; i++) { ThreadPool.QueueUserWorkItem((state) => { // Execute the method Thread.CurrentThread.Name = String.Format("Thread#{0:00}", state); threadProc(); if (Interlocked.Decrement(ref count) == 0) { mre.Set(); } }, i); } // Wait for all work to complete mre.WaitOne(); } }
Following is an example using Parallel.Do method (Note the Parallel.For method seems to perform some internal ‘optimisations’ which results in fewer worker thread’s being spun up).
private void RunParalell(Action threadProc)
{
// Set the stop date
stop = DateTime.Now.AddSeconds(runFor);
// Create the list of actions The test spins up a series of threads to load the dataset which is will randomly read or write to the in-memory cache which is also subject to time outs. The following configure was used:
Cache Expiry 200ms Read Lock 20ms Write Lock 100ms Thread Sleep 50ms Read/Write Ratio 9:1 (9 Reads for every 1 Write) My machine configuration is Del MultiPlex 745, Intel Core 2, 2,394MHz, 4GM RAM running Windows 2003 SP 2. The average execution time (ms) for each load request is summarised in the chart below:
Microsoft’s new parallel library wins out on a 2:1 ratio over the single treaded equivalent, and the Read/Write lock proves more efficient as it locks the resource less often given Read’s are more frequent the Writes as configured by the test ratio. For complete test results, please see the complete spreadsheet or download the sample to play for yourself.
Stay tuned for Part 2 where I will discuss the Silverlight socket implementation, including the complexity of sending multi-part chunks of data for the large images.
List
for (int i = 0; i < threadCount; i++)
{
actions.Add(threadProc);
}
// Run the series of actions
Parallel.Do(actions.ToArray());
}
[/sourcecode]