简体   繁体   中英

Run a continuous task from static main method using tasks?

I'm trying to introduce async/await into a new codebase to have it do a background task every x seconds.

I've got the following, but it just stalls forever without ever running the action (printStuff).

using System;
using System.Threading.Tasks;

namespace ASyncTaskDemo
{
    class Program3
    {    
        public static void Main(string[] args)
        {
            Action action = printStuff;    
            ExecuteEvery(new Task(action), 5000).Wait();
            //simulate the rest of the application stalling until shutdown events.
            Console.ReadLine();
        }

        private static int x = 0;

        private static void printStuff()
        {
            Console.Error.Write(x++);
        }

        private static async Task ExecuteEvery(Task execute, int milliseconds)
        {
            while (true)
            {
                var delay = Task.Delay(milliseconds);
                await Task.WhenAll(delay, execute);
            }
        }
    }
}

How do I make sure that the tasks are running on the right threads, or even running at all?

execute is expected to be a longish running database summary query. milliseconds is expected to be in the range of seconds to minutes, and would appreciate accuracy in timing.

By 'right threads' I mean that the task should execute in an async/networking context, not synchronously with the main program dispatch.

However there may be times when it would be appropriate to run the task in a UI thread or a server thread if this code is to be reusable, and I would appreciate if it was explained what thread and how to configure it.

You never actually start a task. Indeed, IMHO it does not make sense to represent execute as a task. Just pass the delegate itself:

class Program3
{    
    public static void Main(string[] args)
    {
        Action action = printStuff;    
        ExecuteEvery(action, 5000); // ignore returned task
        //simulate the rest of the application stalling until shutdown events.
        Console.ReadLine();
    }

    private static int x = 0;

    private static void printStuff()
    {
        Console.Error.Write(x++);
    }

    private static async Task ExecuteEvery(Action execute, int milliseconds)
    {
        while (true)
        {
            await Task.Delay(milliseconds);
            execute();
        }
    }
}

Note also that if you call Wait() , you'll never read the ReadLine() . I've removed it in my version above.

I've got the following, but it just stalls forever without ever running the action (printStuff).

This happens because the Task you passed to executeEvety was created using the 'Task' constructor, so it will not be scheduled to run if you don't run it explicitly , so by awaiting you wait for an unstarted task to finish - this will never happend.

You should instead pass an Action to executeEvery and invoke it every loop iteration without the need of async invocation:

ExecuteEvery(action, 5000).Wait();

private static async Task ExecuteEvery(Action action, int milliseconds)
{
    while (true)
    {
        await Task.Delay(milliseconds);
        action();
    }
}

If the action is long running and. you want to run it in the background without waiting for it to finish before the next iteration (fire and forget), add Task.Run when you invoke it:

private static async Task ExecuteEvery(Action action, int milliseconds)
{
    while (true)
    {
        await Task.Delay(milliseconds);
        Task.Run(() => action());
    }
}

But wouldn't it be better to just forget about the endless loop and use Timer to achieve what you want?

Creating a Task does not automatically start it. You're awaiting on a Task that will never complete because it is not started. You should start a Task using Task.Run() I have modified your code a bit below:

public static void Main(string[] args)
{
    Action action = printStuff;
    ExecuteEvery(action, 5000).Wait();
    //simulate the rest of the application stalling until shutdown events.
    Console.ReadLine();
}

private static int x = 0;

private static void printStuff()
{
    Console.Error.Write(x++);
}

private static async Task ExecuteEvery(Action execute, int milliseconds)
{
    while (true)
    {
        var delay = Task.Delay(milliseconds);
        await Task.WhenAll(delay, Task.Run(execute));
    }
}

I ended up fixing this by using a Func<Task> instead of a Action<Task>

This enables the use of an async method, instead of a function, and allows the other method to be appropriately decorated with the async keyword.

/// <summary>
/// Executes a task every x milliseconds, but will wait for long running tasks to complete.
/// </summary>
/// <param name="execute">A Task Producer</param>
/// <param name="milliseconds">How often to run execute</param>
/// <param name="cancel">A cancellation token</param>
/// <returns></returns>
private async Task ExecuteEvery(Func<Task> execute, int milliseconds, CancellationToken cancel)
{
    while (true)
    {
        var delay = Task.Delay(milliseconds, cancel);
        await Task.WhenAll(delay, execute());
        if(cancel.IsCancellationRequested) break;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM