0

I'm writing a windows phone app which stores data in a local database. There are multiple threads in my app that access the database and up until this point I have used the technique described here with an AutoResetEvent to ensure that only one thread can access the database at any one time.

So far this has worked very reliably, but now I want to add a ScheduledTask to do some work in the background so I've potentially got multiple processes now competing for access to the database.

Can anyone advise how I can adapt the AutoResetEvent technique to be used across multiple processes on Windows Phone?

I have seen approaches using a Mutex. If I acquire the Mutex before each DB call and then release it afterwards (similar to the way I'm using AutoResetEvent), will this do the trick? Is there any potential problems with this technique? eg: performance?

Ben Pink
  • 36
  • 6

3 Answers3

2

Ok so first of all my problem was actually 2 problems:

  1. Need to ensure that if the foreground app is running, the background process won't run
  2. Need to ensure that only one thread can access the databasse at once and this needs to work across processes to cater for the (admittedly rare, but possible) scenario where the foreground app is started while the background process is in progress.

Based on the good work done in this thread, I created a couple of classes to help.

To solve problem (1), I created the SingleInstanceSynchroniser:

/// <summary>
/// Used to ensure only one instance (foreground app or background app) runs at once
/// </summary>
public class SingleInstanceSynchroniser : IDisposable
{
    private bool hasHandle = false;
    Mutex mutex;

    private void InitMutex()
    {
        string mutexId = "Global\\SingleInstanceSynchroniser"; 
        mutex = new Mutex(false, mutexId);
    }

    public SingleInstanceSynchroniser()
    {
        InitMutex();
        hasHandle = mutex.WaitOne(0);
    }

    public void Dispose()
    {
        if (hasHandle && mutex != null)
            mutex.ReleaseMutex();
    }

    public bool HasExclusiveHandle { get { return hasHandle; } }

}

Usage:

In App.xaml.cs:

...

SingleInstanceSynchroniser singleInstanceSynchroniser;

public App()
{
    singleInstanceSynchroniser = new SingleInstanceSynchroniser();

...

In ScheduledAgent.cs:

SingleInstanceSynchroniser singleInstanceSynchroniser;

protected override void OnInvoke(ScheduledTask task)
    {
        singleInstanceSynchroniser = new SingleInstanceSynchroniser();

        if (singleInstanceSynchroniser.HasExclusiveHandle)
        {
            //Run background process
            ...
        }
        else
        { //Do not run if foreground app is running
            NotifyComplete();
        }
    }

To solve problem (2), I created the SingleAccessSynchroniser:

/// <summary>
/// Used to ensure only one call is made to the database at once 
/// </summary>
public class SingleAccessSynchroniser : IDisposable
{
    public bool hasHandle = false;
    Mutex mutex;

    private void InitMutex()
    {
        string mutexId = "Global\\SingleAccessSynchroniser"; 
        mutex = new Mutex(false, mutexId);            
    }

    public SingleAccessSynchroniser() : this(0)
    { }

    public SingleAccessSynchroniser(int TimeOut)
    {
        InitMutex();

        if (TimeOut <= 0)
            hasHandle = mutex.WaitOne(); 
        else
            hasHandle = mutex.WaitOne(TimeOut);

        if (hasHandle == false)
            throw new TimeoutException("Timeout waiting for exclusive access on SingleInstance");
    }

    public void Release()
    {
        if (hasHandle && mutex != null)
        {
            mutex.ReleaseMutex();
            hasHandle = false;
        }
    }

    public void Dispose()
    {
        Release();
    }
}

Usage: In all database calls:

using (var dbSync = new SingleAccessSynchroniser())
        {
            //Execute your database calls
        }

This has been running reliably for a few weeks now. Hope someone else finds it useful.

Community
  • 1
  • 1
Ben Pink
  • 36
  • 6
2

I ran into some problems using Bens solution on Windows Phone 8. Please see this thread for a complete documentation of the problems.

I was able to resolve the issues by removing "Global\" from "Global\SingleInstanceSynchroniser".

Derek
  • 31
  • 1
  • 3
1

Concurrent access to a database between an agent and an app shouldn't be an issue. In fact, using Linq2SQL is one of the recommended ways for communicating between the app and agent.

In practice, it's rarely necessary for the app and agent to run at the same time and so it may be more appropriate to prevent that happening instead.

Potential performance issues will be dependent upon what you're doing. You'll need to measure this to see if it's really an issue.

Matt Lacey
  • 65,560
  • 11
  • 91
  • 143
  • If two threads in the foreground app make requests to the database at the same time, then you get one of these errors: InvalidOperationException “The operation cannot be performed because an operation on another thread has not been completed.” InvalidOperationException “The operation cannot be performed during a call to SubmitChanges.” I can't see why this would be any different if the background app tries to access the db, so your comment that concurrent access shouldn't be an issue is a bit confusing? I have found a resolution to this problem. When I get a spare hour I'll post it here :) – Ben Pink Jun 25 '12 at 02:35