Example #1
0
        /// <summary>
        /// Execute a command batch and synchronizes the client data model with the newer records from the server.
        /// </summary>
        public static void Execute(RemoteBatch remoteBatch)
        {
            // This 'try' block will insure that the mutual exclusion locks are released.
            try
            {
                // Maure sure only one thread at a time tries to refresh the data model.
                ClientMarketData.refreshMutex.WaitOne();

                // IMPORTANT CONCEPT: The rowVersion keeps track of the state of the client in-memory database. The server returns
                // a value for us to use on the next cycle.  If, for any reason, the merge of the data on the client should fail,
                // we won't use the new value on the next cycle. That is, we're going to keep on asking for the same incremental
                // set of records until we've successfully merged them.  This guarantees that the client and server databases stay
                // consistent with each other.
                DateTime timeStamp  = ClientMarketData.timeStamp;
                long     rowVersion = ClientMarketData.rowVersion;

                // IMPORTANT CONCEPT:  Executing the 'Reconcile' Web Service with a rowVersion returns to the client a DataSet with
                // only records that are the same age or younger than the rowVersion. This reduces the traffic on the network to
                // include only the essential records.  This set also contains the deleted records.  When the DataSet that is
                // returned from this Web Service is merged with the current data, the client will be reconciled with the server as
                // of the 'rowVersion' returned from the server.
                DataSet   resultSet;
                WebClient webClient = new WebClient();
                DataSet   dataSet   = webClient.ExecuteAndReconcile(ref timeStamp, ref rowVersion, remoteBatch, out resultSet);
                remoteBatch.Merge(resultSet);

                // If the time stamps are out of sync, then the server has reset since our last refresh (or this is the first time
                // refreshing the data mdoel).  This will reset the client side of the data model so that, after this refresh, the
                // client and the server will be in sync again.
                if (ClientMarketData.timeStamp != timeStamp)
                {
                    ClientMarketData.rowVersion = 0;
                    ClientMarketData.timeStamp  = timeStamp;
                    ClientMarketData.Clear();
                }

                // Optimization: Don't merge the results if there's nothing to merge.
                if (rowVersion > ClientMarketData.rowVersion)
                {
                    // IMPORTANT CONCEPT: This broadcast can be used to set up conditions for the data event handlers.  Often,
                    // optimizing algorithms will be used to consolidate the results of a merge.  This will allow the event driven
                    // logic to clear previous results and set up initial states for handling the bulk update of data.
                    ClientMarketData.OnBeginMerge(typeof(ClientMarketData));

                    // This 'try' block will insure that the locks are released if there's an error.
                    try
                    {
                        // IMPORTANT CONCEPT: Make sure that there aren't any nested locks.  Table locks have to be aquired in the
                        // same order for every thread that uses the shared data model.  It's possible that the current thread may
                        // have locked table 'B' if the preamble, then try to lock table 'A' during the processing of a command
                        // batch.  If another thread aquires the lock on table 'A' and blocks on table 'B', we will have a
                        // deadlock.  This is designed to catch situations where the 'Execute' method is called while tables are
                        // already locked.
                        Debug.Assert(!ClientMarketData.AreLocksHeld);

                        // IMPORTANT CONCEPT: Since the results will still be on the server if the client misses a refresh cycle,
                        // we take the attitude that this update process doesn't have to wait for locks. That is, if we can't get
                        // all the tables locked quickly, we'll just wait until the next refresh period to get the results. This
                        // effectively prevents deadlocking on the client. Make sure all the tables are locked before populating
                        // them.
                        foreach (TableLock tableLock in ClientMarketData.TableLocks)
                        {
                            tableLock.AcquireWriterLock(ClientTimeout.LockWait);
                        }

                        // IMPORANT CONCEPT:  Once all the write locks have been obtained, we can merge the results.  This will
                        // trigger the events associated with the tables for updated and deleted rows.  Also, if
                        // "AcceptChanges" is invoked for a DataSet or a Table, every single record in the DataSet or DataTable
                        // will be "Committed", even though they are unchanged.  This is a VERY inefficient operation.  To get
                        // around this, an ArrayList of modified records is constructed during the Merge operation.  After the
                        // merge, only the new records are committed.
                        ClientMarketData.mergedRows.Clear();
                        ClientMarketData.Merge(dataSet);
                        for (int index = 0; index < ClientMarketData.mergedRows.Count; index++)
                        {
                            ((DataRow)ClientMarketData.mergedRows[index]).AcceptChanges();
                        }

                        // If the merge operation was successful, then we can use the new rowVersion for the next cycle. Any
                        // exception before this point will result in a request of the same set of data becaue the rowVersion was
                        // never updated.
                        ClientMarketData.rowVersion = rowVersion;
                    }
                    catch (ConstraintException)
                    {
                        // Write out the exact location of the error.
                        foreach (DataTable dataTable in ClientMarketData.Tables)
                        {
                            foreach (DataRow dataRow in dataTable.Rows)
                            {
                                if (dataRow.HasErrors)
                                {
                                    Console.WriteLine("Error in '{0}': {1}", dataRow.Table.TableName, dataRow.RowError);
                                }
                            }
                        }
                    }
                    catch (Exception exception)
                    {
                        // Write the error and stack trace out to the debug listener
                        Debug.WriteLine(String.Format("{0}, {1}", exception.Message, exception.StackTrace));
                    }
                    finally
                    {
                        // No matter what happens above, we need to release the locks acquired above.
                        foreach (TableLock tableLock in ClientMarketData.TableLocks)
                        {
                            if (tableLock.IsWriterLockHeld)
                            {
                                tableLock.ReleaseWriterLock();
                            }
                        }
                    }

                    // IMPORTANT CONCEPT: When the merge is complete and the tables are unlocked, this will broadcast an event
                    // which allows optimization code to consolidate the results, examine the changed values and update reports
                    // based on the changed data.
                    ClientMarketData.OnEndMerge(typeof(ClientMarketData));
                }
            }
            finally
            {
                // Other threads can now request a refresh of the data model.
                ClientMarketData.refreshMutex.ReleaseMutex();
            }

            // Throw a specialized exception if the server returned any errors in the RemoteBatch structure.
            if (remoteBatch.HasExceptions)
            {
                throw new BatchException(remoteBatch);
            }
        }
Example #2
0
        /// <summary>
        /// ThreadBackground
        /// </summary>
        /// <remarks>
        /// This procedure runs as a thread.  It will asynchronously query the database for the raw data that makes up the
        /// hierarchy of objects found in the application.  It will then organize the raw, flat data into a hierarchial
        /// tree that's suitable to be viewed in a TreeView control.</remarks>
        private static void ThreadBackground()
        {
            // Create a web client for communicating with the server.
            WebClient webClient = new WebClient();

            // This thread body is executed until the application is terminated.
            while (true)
            {
                try
                {
                    // Maure sure only one thread at a time tries to refresh the data model.
                    ClientMarketData.refreshMutex.WaitOne();

                    // IMPORTANT CONCEPT: The rowVersion keeps track of the state of the client in-memory database. The server
                    // returns a value for us to use on the next cycle.  If, for any reason, the merge of the data on the client
                    // should fail, we won't use the new value on the next cycle. That is, we're going to keep on asking for the
                    // same incremental set of records until we've successfully merged them.  This guarantees that the client and
                    // server databases stay consistent with each other.
                    DateTime timeStamp  = ClientMarketData.timeStamp;
                    long     rowVersion = ClientMarketData.rowVersion;

                    // IMPORTANT CONCEPT:  Executing the 'GetClientMarketData' with a rowVersion returns to the client a DataSet
                    // with only records that are the same age or younger than the rowVersion. This reduces the traffic on the
                    // network to include only the essential records.  We are also merging it with the current ClientMarketData,
                    // which adds new records and records that were deleted by the server.
                    DataSet dataSet = webClient.Reconcile(ref timeStamp, ref rowVersion);

                    // If the time stamps are out of sync, then the server has reset since our last refresh (or this is the first
                    // time refreshing the market data).  This will reset the client side of the data model so that -- after the
                    // current results are merged -- the client and the server will be in sync again.
                    if (ClientMarketData.timeStamp != timeStamp)
                    {
                        ClientMarketData.rowVersion = 0;
                        ClientMarketData.timeStamp  = timeStamp;
                        ClientMarketData.Clear();
                    }

                    // Optimization: Don't merge the results if there's nothing to merge.
                    if (rowVersion > ClientMarketData.rowVersion)
                    {
                        // IMPORTANT CONCEPT: This broadcast can be used to set up conditions for the data event handlers. Often,
                        // optimizing algorithms will be used to consolidate the results of a merge.  This will allow the event
                        // driven logic to clear previous results and set up initial states for handling the bulk update of data.
                        ClientMarketData.OnBeginMerge(typeof(ClientMarketData));

                        try
                        {
                            // IMPORTANT CONCEPT: Since the results will still be on the server if the client misses a refresh
                            // cycle, we take the attitude that this update process doesn't have to wait for locks. That is, if we
                            // can't get all the tables locked quickly, we'll just wait until the next refresh period to get the
                            // results. This effectively prevents deadlocking on the client. Make sure all the tables are locked
                            // before populating them.
                            foreach (TableLock tableLock in ClientMarketData.TableLocks)
                            {
                                tableLock.AcquireWriterLock(ClientTimeout.LockWait);
                            }

                            // IMPORANT CONCEPT:  Once all the write locks have been obtained, we can merge the results.  This will
                            // trigger the events associated with the tables for updated and deleted rows.  Also, if
                            // "AcceptChanges" is invoked for a DataSet or a Table, every single record in the DataSet or DataTable
                            // will be "Committed", even though they are unchanged.  This is a VERY inefficient operation.  To get
                            // around this, an ArrayList of modified records is constructed during the Merge operation.  After the
                            // merge, only the new records are committed.
                            ClientMarketData.mergedRows.Clear();
                            ClientMarketData.Merge(dataSet);
                            for (int index = 0; index < ClientMarketData.mergedRows.Count; index++)
                            {
                                ((DataRow)ClientMarketData.mergedRows[index]).AcceptChanges();
                            }

                            // If the merge operation was successful, then we can use the new rowVersion for the next cycle. Any
                            // exception before this point will result in a request of the same set of data becaue the rowVersion
                            // was never updated.
                            ClientMarketData.rowVersion = rowVersion;
                        }
                        catch (ApplicationException applicationException)
                        {
                            // Tyipcally, a failure to gain a lock will invoke this exception.  If this information ends up being
                            // too much for the log device, we can alway inhibit exception catching here.
                            Debug.WriteLine(applicationException.Message);
                        }
                        catch (ConstraintException)
                        {
                            // Write out the exact location of the error.
                            foreach (DataTable dataTable in ClientMarketData.Tables)
                            {
                                foreach (DataRow dataRow in dataTable.Rows)
                                {
                                    if (dataRow.HasErrors)
                                    {
                                        Console.WriteLine("Error in '{0}': {1}", dataRow.Table.TableName, dataRow.RowError);
                                    }
                                }
                            }
                        }
                        catch (Exception exception)
                        {
                            // Write the error and stack trace out to the debug listener
                            Debug.WriteLine(String.Format("{0}, {1}", exception.Message, exception.StackTrace));
                        }
                        finally
                        {
                            // No matter what happens above, we need to release the locks acquired above.
                            foreach (TableLock tableLock in ClientMarketData.TableLocks)
                            {
                                if (tableLock.IsWriterLockHeld)
                                {
                                    tableLock.ReleaseWriterLock();
                                }
                            }
                        }

                        // IMPORTANT CONCEPT: When the merge is complete, this will broadcast an event which allows optimization code
                        // to consolidate the results, examine the changed values and update reports based on the changed data.
                        ClientMarketData.OnEndMerge(typeof(ClientMarketData));
                    }
                }
                catch (Exception exception)
                {
                    // Write the error and stack trace out to the debug listener
                    Debug.WriteLine(String.Format("{0}, {1}", exception.Message, exception.StackTrace));
                }
                finally
                {
                    // Other threads can now request a refresh of the data model.
                    ClientMarketData.refreshMutex.ReleaseMutex();
                }

                // Wait for a predetermined interval before refreshing the data model again.
                Thread.Sleep(ClientTimeout.RefreshInterval);
            }
        }