2
\$\begingroup\$

I have the following poller class that can be used to "poll the required action":

public class Poller
{
    private readonly string name;
    private readonly Action action;
    private readonly int pollingInterval;
    private readonly Thread pollingThread;

    private readonly AutoResetEvent stopEvent;
    private readonly ManualResetEventSlim pauseEvent;

    private readonly object locker = new object();
    private PollerState pollerState;

    public Poller(string name, Action action, int pollingInterval) :
        this(name, action, pollingInterval, true)
    { }

    public Poller(string name, Action action, int pollingInterval, bool isBackground)
    {
        this.name = name;
        this.action = action;
        this.pollingInterval = pollingInterval;

        stopEvent = new AutoResetEvent(false);
        pauseEvent = new ManualResetEventSlim(false);

        pollingThread = new Thread(DoWork)
        {
            Name = name,
            IsBackground = isBackground
        };
    }

    public void Start()
    {
        pollerState = PollerState.Running;
        pollingThread.Start();
    }

    public void Start(int dueTime)
    {
        new Timer(o => Start(), null, dueTime, Timeout.Infinite);
    }

    public void Stop()
    {
        lock (locker)
        {
            if (pollerState != PollerState.Running && pollerState != PollerState.PauseRequested)
                Log.Trace("Poller.Stop(): Stop requested on " + 
                    $"\"{name}\", current state {pollerState}");

            pollerState = PollerState.StopRequested;
            stopEvent.Set();
            pauseEvent.Set();
        }
    }

    public void Pause()
    {
        lock (locker)
        {
            if (pollerState != PollerState.Running)
                Log.Trace("Poller.Stop(): Pause requested on " +
                    $"\"{name}\", current state {pollerState}");

            pauseEvent.Reset();
            pollerState = PollerState.PauseRequested;
        }
    }

    public void Continue()
    {
        lock (locker)
        {
            // Applicable if job is long running or no new poll was needed since pause requested.
            if (pollerState == PollerState.PauseRequested)
                pollerState = PollerState.Running;
            else if (pollerState != PollerState.Paused)
            {
                Log.Trace("Poller.Stop(): Continue requested on " +
                    $"\"{name}\", current state {pollerState}");
            }
            pauseEvent.Set();
        }
    }

    private void DoWork()
    {
        while (pollerState == PollerState.Running)
        {
            try
            {
                action();
            }
            catch (Exception ex)
            {
                Log.Error(ex);
            }
            finally
            {
                if (stopEvent.WaitOne(pollingInterval))
                {
                    if (pollerState == PollerState.StopRequested)
                        pollerState = PollerState.Stopped;
                }

                if (pollerState == PollerState.PauseRequested)
                {
                    pollerState = PollerState.Paused;
                    pauseEvent.Wait();

                    // Continue only if we are still in Pause mode and not StopRequested.
                    if (pollerState == PollerState.Paused)
                        pollerState = PollerState.Running;
                }
            }
        }
        Log.Trace($"Poller.Stop(): Exiting \"{name}\" poller");
    }
}

public enum PollerState
{
    NotStarted = 0,
    Running,
    PauseRequested,
    Paused,
    StopRequested,
    Stopped
};

Firstly, I want to know how this could be improved. Second, I want to use this to poll whether an SqlConnection is "active" or has ConnectionState.Open. To do this I wrote a little test class to test the poller:

[TestClass]
public class PollerTests
{
    private SqlConnection connection;
    private const string connectionString =
        "Data Source=localhost;Initial Catalog=master;Integrated Security=True;" +
        "MultipleActiveResultSets=True;Connection Timeout = 0";
    private ManualResetEventSlim connectionSeveredEvent;
    private Timer severedTimer;

    [TestInitialize]
    public void Initialize()
    {
        connectionSeveredEvent = new ManualResetEventSlim(false);
        connection = new SqlConnection(connectionString);
        connection.Open();
    }

    [TestCleanup]
    public void CleanUp()
    {
        connection.Dispose();
    }

    [TestMethod]
    public void SqlConnectionStatePollerTest()
    {
        Poller sqlConnectionPoller = new Poller("SqlConnection", TestConnectionWithOutput, 10);
        sqlConnectionPoller.Start();

        connectionSeveredEvent.Wait();
        Assert.IsTrue(connectionSeveredEvent.IsSet);
        Assert.IsTrue(connection.State == ConnectionState.Closed);
    }

    private void TestConnectionWithOutput()
    {
        bool ok = connection.TestConnectionStateOpen();
        if (!ok)
            connectionSeveredEvent.Set();
        else
        {
            if (connection.State == ConnectionState.Open)
                connection.Close();
        }
    }
}

This does one itteration of the poller, on the second it sets the connection state to closed and the reset event is set and the test passes. The test code is:

public static SomeUtilityCalss
{
    public static bool TestConnectionStateOpen(this SqlConnection connection)
    {
        if (connection.State == ConnectionState.Closed)
            return false;

        string sql = "SELECT 1;";
        return SqlHelper.ExecuteScalar<int>(connection, CommandType.Text, sql) == 1;
    }
}

Any comments on this code would be appreciated. Particularly, on my use of Thread over Task...

\$\endgroup\$
  • 1
    \$\begingroup\$ "I want to use this to poll whether an SqlConnection is active or has ConnectionState.Open." But... why? Let the connection pool deal with that. Don't "manage" SqlConnection like that, instead use it properly: using (SqlConnection con = new SqlConnection(connectionString)). \$\endgroup\$ – BCdotWEB Aug 4 '16 at 12:47
  • \$\begingroup\$ I want to reflect any connections that were connected but that have since been changed or severed. Of course I wrap my connection as you suggest, but in my GUI I allow multiple connections. If the user connects, but then the server is brought down externally I want to know about it... connection.OnStateChanged event cannot handle this... \$\endgroup\$ – MoonKnight Aug 4 '16 at 12:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.