Complex Synchronization Wrapper

One of the more complex tasks in building offline-enabled (aka occasionally connected/disconnected) software is how you handle synchronization. Most synchronization frameworks typically handle synchronization of one form of data. For example Mobile Services allow for synchronization of individual tables based on a query. However, for most application this isn’t sufficient – they may require database synchronization in addition to uploading of new images taken on the device and downloading any associated documents for offline viewing. This means you need a synchronization layer that can co-ordinate synchronization of different data types/formats/processes.

There may be times within the application where you don’t want to perform a full synchronization. For example if the user creates a new record, the application should attempt to push this new record to the server immediately but it may not want to do a full synchronization until the user hits the sync button.This means you need a mechanism where you can partition the synchronization layer and only trigger synchronization of individual parts as required.

Here’s an example of what the synchronization wrapper might look like in action:

[Flags]
public enum SyncStages
{
    None=0,
    Stage1=1,
    Stage2=2,
    Stage3=4,
    All = Stage1 | Stage2 | Stage3
}

public class TestSynchronization     {
    public SynchronizationContext<SyncStages> SynchronizationManager { get; set; }

    public Action<string> Progress { get; set; }

    public TestSynchronization()
    {
        SynchronizationManager = new SynchronizationContext<SyncStages>();
        SynchronizationManager.DefineSynchronizationStep(SyncStages.Stage1, Step1);
        SynchronizationManager.DefineSynchronizationStep(SyncStages.Stage2, Step2);
        SynchronizationManager.DefineSynchronizationStep(SyncStages.Stage3, Step3);
        SynchronizationManager.SynchronizationChanged += SynchronizationManager_SynchronizationProgressChanged;
    }

    private void SynchronizationManager_SynchronizationProgressChanged(object sender, SynchronizationEventArgs<SyncStages> e)
    {
        var message = e.ToString();
        Progress(message);
    }

    private const int Step1Stages = 5;

    public async Task<bool> Step1(ISynchronizationStage<SyncStages> step)
    {
        step.RegisterSubStagesCount(Step1Stages);
        for (int i = 0; i < Step1Stages; i++)
        {
            step.StartSubStage();
            await Task.Delay(1000, step.CancellationToken);
            step.EndSubStage();
            if (step.CancellationToken.IsCancellationRequested) return false;
        }
        return true;
    }

    public async Task<bool> Step2(ISynchronizationStage<SyncStages> step)
    {
        await Task.Delay(2*1000, step.CancellationToken);
        return true;
    }

    public async Task<bool> Step3(ISynchronizationStage<SyncStages> step)
    {
        step.RegisterSubStages(Step3Stages.S3Stage1, Step3Stages.S3Stage2, Step3Stages.S3Stage3);

        await step.RunSubStage(Step3Stages.S3Stage1, Step3Sub);

        await step.RunSubStage(Step3Stages.S3Stage2, Step3Sub);

    await model.SynchronizationManager.Synchronize(SyncStages.Stage1 | SyncStages.Stage2,
        waitForSynchronizationToComplete: true);
}

private async void FullSynchronizeClick(object sender, RoutedEventArgs e)
{
    await model.SynchronizationManager.Synchronize(SyncStages.All,
        cancelExistingSynchronization:true,
        waitForSynchronizationToComplete: true);

        return true;
    }

    private enum Step3Stages
    {
        S3Stage1,
        S3Stage2,
        S3Stage3
    }

    private async Task<bool> Step3Sub(ISynchronizationStage<Step3Stages> step)
    {
        step.Progress(0.2);
        await Task.Delay(2000);
        step.Progress(0.7);
        await Task.Delay(2000);
        step.Progress(1.0);
        return true;
    }

}

Triggering and/or cancelling synchronization can then be done using the following:

await model.SynchronizationManager.Synchronize(SyncStages.Stage1 | SyncStages.Stage2, waitForSynchronizationToComplete: true);
await model.SynchronizationManager.Synchronize(SyncStages.All,
                cancelExistingSynchronization:true,
                waitForSynchronizationToComplete: true);
await model.SynchronizationManager.Cancel(true);

The first line triggers stages 1 and 2 to be synchronized – it won’t cancel any existing synchronization process and will only return once it has completed the synchronization process; The second line triggers all stages to be synchronized and will cancel any existing synchronization process; The third line will cancel any existing synchronization and only return once they’ve been cancelled.

I’ve attached a first pass at an implementation of such a sync framework. Note that the actual sync logic is in the steps shown above, it’s the framework for scheduling them and reporting progress which is being shown in the sample.

SynchronizationWrapper.zip (48.4KB)

Leave a comment