Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call a function periodically using BackgroundWorker

I have a C# windows form application. I want to update some labels by fetching information from the web. I want to call a function periodically using BackgroundWorker.

public partial class OptionDetails : Form
{
    static System.ComponentModel.BackgroundWorker worker = new System.ComponentModel.BackgroundWorker();
    static void fun()
    {
       worker.DoWork += new DoWorkEventHandler(worker_DoWork);
       worker.RunWorkerCompleted += worker_RunWorkerCompleted;
       worker.WorkerSupportsCancellation = true;
       worker.RunWorkerAsync();
    }
    static void worker_DoWork(object sender, DoWorkEventArgs e)
    { // some work }

    static void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    { // on completion }
}

If I use Timer, the UI hangs. How do I use BackgroundWorker to call worker_DoWork periodically?

My actual code:

public partial class myform: Form
{
    public myform()
    {
        InitializeComponent();
    }

    public async Task Periodic(Action func, TimeSpan period, CancellationToken token)
    {
        while (true)
        {
            // throws an exception if someone has requested cancellation via the token.
            token.ThrowIfCancellationRequested();

            func();

            // asynchronously wait 
            await Task.Delay(period);
        }
    }

    public async void hello()
    {
        await Periodic(getCurrentInfo, TimeSpan.FromSeconds(2), CancellationToken.None);
    }

    private void myform_Load(object sender, EventArgs e)
    {
        hello();
    }


    private void getCurrentInfo()
    {
        WebDataRetriever wdr = new WebDataRetriever();
        string name = "name";
        string url = String.Empty;
        string[] prices = new string[2];
        bool urlExists = url.TryGetValue(name, out url);
        if (urlExists)
        {
            wdr.processurl();  // time consuming function
            prices[0] = wdr.price1;
            prices[1] = wdr.price2;

            System.Globalization.NumberFormatInfo nfo = new System.Globalization.CultureInfo("en-US", false).NumberFormat;
            if (prices != null)
            {
                // change labels
            }
        }
    }

}
like image 538
rako Avatar asked Jan 25 '26 04:01

rako


1 Answers

The simplest solution for what you need is probably to use a Timer to kick off BackgroundWorker, but using async/await I believe results in a more compact and elegant solution.

The solution below is an asynchronous method, for which the compiler generates a state machine. When the asynchronous method Periodic is invoked it starts executing up until the first await statement. In this case this is:

 await Task.Delay(period);

The expression awaited returns an awaitable, which is in this case a Task, but it can be anything that has a method GetAwaiter which returns a type implementing the INotifyCompletion interface or the ICriticalNotifyCompletion interface.

If this task is complete the method continues executing synchronously, if the task is not complete the method returns. Once the task is complete, execution of the method resumes after that await statement in the same SynchronizationContext. If you called this from a GUI thread, execution will resume in the GUI thread, but for your case it will resume on a ThreadPool thread because console apps do not have a SynchronizationContext.

Task.Delay(period) returns a Task that becomes complete when the period elapses. I.e. it is like an asynchronous version of Thread.Sleep so execution of the while loop resumes after period expires.

The end result is that the loop runs forever periodically checking for cancellation (in which case an OperationCancelledException is thrown) and executing func.

public static async Task Periodic(Action func, TimeSpan period, CancellationToken token)
{
    while(true)
    {
        // throws an exception if someone has requested cancellation via the token.
        token.ThrowIfCancellationRequested();

        func();

        // asynchronously wait 
        await Task.Delay(period);
    }
}

In a GUI app you can use it like this:

await Periodic(() => /* do something clever here*/, TimeSpan.FromSeconds(1), CancellationToken.None);

The main method of a console app cannot be asynchronous so you cannot use await but you can call Wait() on the result instead to run it indefinitely and prevent the application from exiting.

void Main()
{
    Periodic(() => Console.WriteLine("Hello World!"), TimeSpan.FromSeconds(1), CancellationToken.None).Wait();
}

Care must be taken when calling Wait() in a GUI app as it can result in a deadlock.

Update

You may also benefit from having an overload of Periodic that takes an async func, in case you have an expensive time consuming function you want to run in the background.

public async Task Periodic(Func<Task> func, TimeSpan period, CancellationToken token)
{
    while (true)
    {
        // throws an exception if someone has requested cancellation via the token.
        token.ThrowIfCancellationRequested();

        await func();

        // asynchronously wait 
        await Task.Delay(period);
    }
}
like image 95
NeddySpaghetti Avatar answered Jan 27 '26 18:01

NeddySpaghetti



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!