firehotguitar88
firehotguitar88

Reputation: 59

having trouble with a timer in C# code

I'm having issues implementing a timer in C# code. high level overview of what I'm doing, i have a WPF app that updates data displayed to the user every three seconds based on a data feed. Most of the data on the window changes every three seconds but I'm trying to do an SQL query for a count and i only want the data to update every 5 minutes. So I built the SQL query shown below and the timer also shown below but the timer expects it to be a void when it had to return a string and i'm not sure how to get past this.

My original attempt at using a Timer (this runs in my main method):

Timer t = new Timer(TimeSpan.FromMinutes(5).TotalMilliseconds); // set the time (5 min in this case)
            t.AutoReset = true;
            t.Elapsed += new System.Timers.ElapsedEventHandler(SQLDataTotalCalls());
            t.Start();

My Method for the SQL Query:

public string SQLDataTotalCalls(object sender, ElapsedEventArgs a)
{
    DateTime dte = DateTime.Today;

    string fixedStartDate = String.Format("{0:yyyy-MM-dd " + "05:00:00.000" + "}", dte);
    string fixedEndDate = String.Format("{0:yyyy-MM-dd " + "05:00:00.000" + "}", dte.AddDays(1));

    SqlConnection connection = null;
    try
    {
        var dataSet = new DataSet();

        connection = new SqlConnection("Data Source=QL1OADB1;Initial Catalog=OADB;User Id=username;Password=password;
        connection.Open();

        var command = new SqlCommand("SELECT COUNT(SOURCEID) AS 'MYCOUNT' "
                                     + "FROM [OADB].[oadb].[CmsCallHistory] "
                                     + "WHERE disposition = 2 and DISPSPLIT in (" + SkillNumber + ") AND SEGSTOP BETWEEN '" + fixedStartDate + "' and '" + fixedEndDate + "'",
            connection)
        {
            CommandType = CommandType.Text
        };

        var dataAdapter = new SqlDataAdapter { SelectCommand = command };

        dataAdapter.Fill(dataSet);

        return dataSet.Tables[0].Rows[0]["MYCOUNT"].ToString();

    }
    catch (Exception e)
    {
        throw new Exception(e.Message, e);
    }
    finally
    {
        if (connection != null)
        {
            connection.Close();
        }
    }
}

Update:

Per the suggested answer below, I've changed the above to this:

async Task RunPeriodicQueryTotalCalls()
{
    TimeSpan interval = TimeSpan.FromMinutes(5);

    while (true)
    {
        await Task.Delay(interval);

        string result = await Task.Run(SQLDataTotalCalls);

        TotalDailyCalls = result;
    }
}

I've updated my method for the SQL Query to have this declaration:

public string SQLDataTotalCalls() { ... }

But I get a compiler error:

error CS0121: The call is ambiguous between the following methods or properties: 'Task.Run<TResult>(Func<TResult>)' and 'Task.Run(Func<Task>)'

Upvotes: 1

Views: 90

Answers (2)

Peter Duniho
Peter Duniho

Reputation: 70701

Supposing C# would let you subscribe your string-returning method to the void-declared event. What code, exactly, do you think would actually be receiving that return value? Where would you use it?

There isn't much context in your question, which makes it difficult, if not impossible, to understand exactly what you want the code to do. There's nothing in your question that indicates how you'd get the returned string value, nor what you'd do with it.

All that said, I think it likely your needs are better served using the Task Parallel Library API instead of a timer. This will allow you to easily move return values from background task to UI thread, as well as handle the timing for you.

For example:

async Task RunPeriodicQuery()
{
    TimeSpan interval = TimeSpan.FromMinutes(5);

    while (true)
    {
        await Task.Delay(interval);

        string result = await Task.Run((Func<string>)SQLDataTotalCalls);

        // do something with "result" here...you'll be in the UI thread, so make it quick
    }
}

In the above, you would remove the parameters from your SQLDataTotalCalls() method declaration.

You will need to call the method RunPeriodicQuery() once, in some part of your code where you are initializing and would have otherwise created and started the timer. The implementation above uses an inifite while (true) loop, but of course you can introduce any mechanism you want to allow the method to exit if and when you want it to stop operating, such as using a bool field instead of true to control the loop operation.

If that does not address your question satisfactorily, please improve the question by editing it so that it includes a good Minimal, Complete, and Verifiable code example that shows clearly what you've tried, explain precisely what the code does, and what you want it to do instead.

Upvotes: 2

Rick Hodder
Rick Hodder

Reputation: 2252

  1. Your code is calling the method, rather than pointing the event to your method.

    // notice there are no parens, that's because you are pointing it at a method, not running the method

    t.Elapsed += new System.Timers.ElapsedEventHandler(SQLDataTotalCalls);

A simpler way of doing this is:

            t.Elapsed += SQLDataTotalCalls;
  1. The timer should only be used to fire an event, not return a value. What is done in the event delegate (SQLDataTotalCalls) should pass the string to some other object or service to save the string.

  2. You will need to change the signature of your SQLDataTotalCalls method to return void, so that it will match the signature required for the timer handler

Upvotes: 2

Related Questions