同时触发重复性任务 [英] Firing Recurring Tasks At The Same Time

查看:60
本文介绍了同时触发重复性任务的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在特定时间点同时触发 2 个任务,然后重新开始.例如,下面是等待 1 分钟的任务和等待 5 分钟的第二个任务.1 分钟任务应在 5 分钟内触发 5 次,5 分钟任务应在 10 分钟内触发 10 次,5 分钟任务应在 10 分钟内触发 2 次,以此类推.但是,我需要 1 分钟的任务与 5 分钟的任务同时触发.

I am trying to get 2 tasks to fire at the same time at a specific point in time, then do it all over again. For example, below is a task that waits 1 minute and a second task that waits 5 minutes. The 1 minute task should fire 5 times in 5 minutes and the 5 minute task 1 time, the 1 minute task should fire 10 times in 10 minutes and the 5 minute task 2 times, on and on and on. However, I need the 1 minute task to fire at the same time as the 5 minute.

我能够使用 System.Timers 做到这一点,但是在我最终需要的多线程中并不能很好地发挥作用.System.Thread 没有任何等同于 System.Timers AutoReset 的东西,除非我遗漏了什么.

I was able to do this with System.Timers but that did not play well with the multithreading that I eventually needed. System.Thread did not have anything equivalent to System.Timers AutoReset unless I'm missing something.

我下面是两个延迟计时器同时启动,但 t1 只触发 1 次而不是 5 次.本质上它需要一直持续到程序停止而不是 X 次.

What I have below is both delay timers start at the same time BUT t1 only triggers 1 time and not 5. Essentially it needs to keep going until the program is stopped not just X times.

            int i = 0;
            while (i < 1)
            {

                Task t1 = Task.Run(async delegate
                {
                    await Task.Delay(TimeSpan.FromMinutes(1));
                    TaskWorkers.OneMinuteTasks();
                });
                //t1.Wait();

                Task t2 = Task.Run(async delegate
                {
                    await Task.Delay(TimeSpan.FromMinutes(5));
                    TaskWorkers.FiveMinuteTasks();
                });
                t2.Wait();
            } 

更新我首先阅读了下面关于向任务添加内部循环的约翰评论.下面是我想要的.简单的修复.我知道我确实说过只要程序运行我就希望它运行,但我能够计算出我实际需要的最大循环数.×<10 只是我选择的一个数字.

Update I first read Johns comment below about just adding an inner loop to the Task. Below works as I was wanting. Simple fix. I know I did say I would want this to run for as long as the program runs but I was able to calculate out the max number of loops I would actually need. x < 10 is just a number I choose.

                Task t1 = Task.Run(async delegate
                    {
                        for(int x = 0; x < 10; x++)
                        {
                            await Task.Delay(TimeSpan.FromMinutes(1));
                            TaskWorkers.OneMinuteTasks();
                        }
                    });

                Task t2 = Task.Run(async delegate
                {
                    for (int x = 0; x < 10; x++)
                    {
                        await Task.Delay(TimeSpan.FromMinutes(5));
                        TaskWorkers.FiveMinuteTasks();
                    }
                });

据我所知,没有 CPU 或内存的严重使用.

As far as I can tell no gross usage of CPU or memory.

推荐答案

你可以有一个循环,以协调的方式周期性地触发任务:

You could have a single loop that periodically fires the tasks in a coordinated fashion:

async Task LoopAsync(CancellationToken token)
{
    while (true)
    {
        Task a = DoAsync_A(); // Every 5 minutes
        for (int i = 0; i < 5; i++)
        {
            var delayTask = Task.Delay(TimeSpan.FromMinutes(1), token);
            Task b = DoAsync_B(); // Every 1 minute
            await Task.WhenAll(b, delayTask);
            if (a.IsCompleted) await a;
        }
        await a;
    }
}

此实现在开始新的 1 分钟循环之前等待 B 任务和 Task.Delay 任务完成,因此如果 B 任务运行时间非常长,则计划会滑动.这可能是一种可取的行为,除非您同意任务重叠的可能性.

This implementation awaits both the B task and the Task.Delay task to complete before starting a new 1-minute loop, so if the B task is extremely long-running, the schedule will slip. This is probably a desirable behavior, unless you are OK with the possibility of overlapping tasks.

如果 A 或 B 任务出现异常,循环将在一分钟检查点报告失败.这并不理想,但让循环对错误做出完美响应会使代码变得相当复杂.

In case of an exception in either the A or B task, the loop will report failure at the one minute checkpoints. This is not ideal, but making the loop perfectly responsive on errors would make the code quite complicated.

更新:这是一个高级版本,在出现异常时响应更快.它使用链接的 CancellationTokenSource,当两个任务中的任何一个失败时自动取消,然后导致延迟任务立即取消.

Update: Here is an advanced version that is more responsive in case of an exception. It uses a linked CancellationTokenSource, that is automatically canceled when any of the two tasks fails, which then results to the immediate cancellation of the delay task.

async Task LoopAsync(CancellationToken token)
{
    using (var linked = CancellationTokenSource.CreateLinkedTokenSource(token))
    {
        while (true)
        {
            Task a = DoAsync_A(); // Every 5 minutes
            await WithCompletionAsync(a, async () =>
            {
                OnErrorCancel(a, linked);
                for (int i = 0; i < 5; i++)
                {
                    var delayTask = Task.Delay(TimeSpan.FromMinutes(1),
                        linked.Token);
                    await WithCompletionAsync(delayTask, async () =>
                    {
                        Task b = DoAsync_B(); // Every 1 minute
                        OnErrorCancel(b, linked);
                        await b;
                        if (a.IsCompleted) await a;
                    });
                }
            });
        }
    }
}

async void OnErrorCancel(Task task, CancellationTokenSource cts)
{
    try
    {
        await task.ConfigureAwait(false);
    }
    catch
    {
        cts.Cancel();
        //try { cts.Cancel(); } catch { } // Safer alternative
    }
}

async Task WithCompletionAsync(Task task, Func<Task> body)
{
    try
    {
        await body().ConfigureAwait(false);
    }
    catch (OperationCanceledException)
    {
        await task.ConfigureAwait(false);
        throw; // The task isn't faulted. Propagate the exception of the body.
    }
    catch
    {
        try
        {
            await task.ConfigureAwait(false);
        }
        catch { } // Suppress the task's exception
        throw; // Propagate the exception of the body
    }
    await task.ConfigureAwait(false);
}

这个版本的逻辑比最初的简单版本明显更复杂(这使得它更容易出错).CancellationTokenSource 的引入产生了处置它的需要,这反过来又强制确保所有任务都将在异步方法的每个出口点完成.这就是使用 WithCompletionAsync 方法将每个任务后面的所有代码包含在 LoopAsync 方法中的原因.

The logic of this version is significantly more perplexed than the initial simple version (which makes it more error prone). The introduction of the CancellationTokenSource creates the need for disposing it, which in turn makes mandatory to ensure that all tasks will be completed on every exit point of the asynchronous method. This is the reason for using the WithCompletionAsync method to enclose all code that follows every task inside the LoopAsync method.

这篇关于同时触发重复性任务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆