【问题标题】:Run a continuous task from static main method using tasks?使用任务从静态主要方法运行连续任务?
【发布时间】:2018-01-09 04:18:42
【问题描述】:

我正在尝试将 async/await 引入一个新的代码库,让它每 x 秒执行一次后台任务。

我有以下内容,但它只是永远停止而没有运行操作 (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);
            }
        }
    }
}

如何确保任务在正确的线程上运行,甚至完全运行?

execute 预计是一个运行时间较长的数据库摘要查询。 毫秒预计在秒到分钟的范围内,并且希望计时准确。

“正确线程”是指任务应该在异步/网络环境中执行,而不是与主程序调度同步。

但是,如果此代码可重用,则有时可能适合在 UI 线程或服务器线程中运行任务,如果能解释一下是什么线程以及如何配置它,我将不胜感激。

【问题讨论】:

  • “在正确的线程上运行”是什么意思?能详细解释一下吗
  • 我添加了一个编辑澄清。
  • 如果你的Action 将是一个 async 调用,即i/o 调用而不是一个cpu 绑定的调用,那么你不应该使用@ 987654325@,对db provider api等使用异步方法
  • 在匿名化和缩小示例时可能会丢失一些东西。

标签: c# async-await


【解决方案1】:

你从来没有真正开始过一项任务。事实上,恕我直言,将execute 表示为一项任务是没有意义的。只需传递委托本身:

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();
        }
    }
}

另请注意,如果您致电Wait(),您将永远不会阅读ReadLine()。我已经在上面的版本中删除了它。

【讨论】:

  • 这不是每 x 毫秒运行一次任务,而是每 x 毫秒运行一次任务 + 可能很长的操作。
  • @RyanTheLeach:是的,你是对的。如果您愿意,请随意使用Task.WhenAll() 方法。这也不是解决这个问题的完美方法......如果任务花费的时间超过指定的延迟,您会在是否尝试赶上、重叠动作等方面变得模棱两可,这似乎超出了您的问题范围。跨度>
【解决方案2】:

我有以下内容,但它只是永远停滞不前 运行动作 (printStuff)。

发生这种情况是因为您传递给 executeEvety 的 Task 是使用“任务”构造函数创建的,因此如果您不显式运行它,它将不会被安排运行,因此通过等待您等待未启动的任务完成 - 这永远不会发生。

您应该将 Action 传递给 executeEvery 并在每次循环迭代时调用它,而无需异步调用:

ExecuteEvery(action, 5000).Wait();

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

如果操作长时间运行并且。如果您想在后台运行它而不等待它在下一次迭代之前完成(即发即弃),请在调用它时添加Task.Run

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

但是忘记无限循环并使用Timer 来实现您想要的不是更好吗?

【讨论】:

  • 这不是每 x 毫秒运行一次任务,而是每 x 毫秒运行一次任务 + 可能很长的操作。
【解决方案3】:

创建Task 不会自动启动它。您正在等待一个永远不会完成的Task,因为它没有启动。您应该使用Task.Run() 启动Task 我在下面修改了您的代码:

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));
    }
}

【讨论】:

  • 有没有办法在原版中使用传入的Task并运行呢?在 while 循环中运行任务是否会更改时间,使其不再是每 x 毫秒,而是 x 毫秒 + 任务时间?
  • 虽然不错,不知道调用代码是否应该负责在Action上调用Task.Run()?再说一次,像其他人所说的那样直接调用Action 可能会更好:)
  • @RyanTheLeach 如果你传入任务,你可以在它上面调用Start(),但是当它在完成的Task 上再次尝试Start() 时会抛出异常,所以它不是好的。您至少也必须传递Action,然后重新创建Task
【解决方案4】:

我最终通过使用Func<Task> 而不是Action<Task> 解决了这个问题

这允许使用 async 方法而不是函数,并允许使用 async 关键字适当地修饰其他方法。

/// <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;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-04-06
    • 2020-06-08
    • 1970-01-01
    • 2018-09-28
    • 2016-09-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多