【问题标题】:Task Inside Loop循环内的任务
【发布时间】:2014-12-16 23:26:35
【问题描述】:

我有一个 Windows 服务,其线程每 2 分钟运行一次。

while (true)
{
    try
    {
        repNeg.willExecuteLoopWithTasks(param1, param2, param3);
        Thread.Sleep(20000);
    }

在这里面我有一个任务循环:

foreach (RepModel repModelo in listaRep)
{
    Task t = new Task(() => { this.coletaFunc(repModelo.EndIp, user, tipoFilial); });
    t.Start();
}

但我认为这个实现是错误的。我只需要为列表中的每个元素运行一个任务, 并且,当特定任务完成时,请稍等片刻,然后重新开始。

M8 我得说我这里有两种情况。

1 - 我等不及所有任务完成。因为有些任务可能需要 2 个小时以上才能完成,而另一些任务可能只需要 27 秒。

2 - 我的任务列表可以更改。这就是为什么我有一个线程。每 2 分钟我的线程获取一个要执行的任务列表,然后开始一个循环。

但有时我的任务还没有完成,另一个线程重新开始,然后我的日志中出现奇怪的事情。 我尝试使用字典来解决我的问题,但经过一段时间的执行,有时需要几天,我的日志显示:

“System.IndexOutOfRangeException”

【问题讨论】:

  • 你不需要任务,只要调用那个函数。
  • 我认为您应该考虑使用 Timer 类。你的实现不好。我会研究异步方法。你不想阻塞线程。
  • @Derek 如果它正在做预期的工作,它是一个阻塞线程吗?
  • 旁注:Thread.Sleep(20000) 将休眠 20 秒,而不是 2 分钟
  • 使用 Thread.Sleep 不是一个好习惯 imo,你所做的就是从线程池中取出一个线程,让它在 2 分钟内什么都不做。那是不好的做法,因为它可能会回到用作其他资源的 threadppol 中。 @D斯坦利

标签: c# task


【解决方案1】:

这就是我要做的......

创建一个存储以下内容(作为属性)的新类:

  • RepModel ID(唯一的)
  • DateTime 最后一次运行
  • int 表示任务应运行的频率(以秒为单位)
  • bool 以确定任务是否正在进行中

然后你需要一个类的全局列表,比如说“JobList”。

您的主应用程序应该有一个计时器,它每隔几分钟运行一次。这个计时器的工作是检查新的RepModel(假设这些可以随着时间而改变,即数据库列表)。当这个勾选时,循环列表并将任何新的(不同的 ID)添加到JobList。您可能还想删除任何不再需要的内容(即从数据库列表中删除)。

然后你有第二个计时器,它每秒运行一次。它的工作是检查JobList 中的所有项目并将上次运行时间与当前时间进行比较(并确保它们尚未在进行中)。如果持续时间已经过去,则开始任务。任务完成后,更新上次运行时间,以便下次运行,确保随时更改“进行中”标志。

这都是理论,您需要自己尝试一下,但我认为它涵盖了您实际想要实现的目标。


一些示例代码(可能编译/工作):

class Job
{
    public int ID { get; set; }
    public DateTime? LastRun { get; set; }
    public int Frequency { get; set; }
    public bool InProgress { get; set; }
}

List<Job> JobList = new List<Job>();

// Every 2 minutes (or whatever).
void timerMain_Tick()
{
    foreach (RepModel repModelo in listaRep)
    {
        if(!JobList.Any(x => x.ID == repModelo.ID)
        {
            JobList.Add(new Job(){ ID = repModel.ID, Frequency = 120 });
        }
    }
}

// Every 10 seconds (or whatever).
void timerTask_Tick()
{
    foreach(var job in JobList.Where(x => !x.InProgress && (x.LastRun == null || DateTime.Compare(x.LastRun.AddSeconds(x.Duration), DateTime.Now) < 0))
    {
        Task t = new Task(() => { 
            // Do task.
        }).ContinueWith(task => {
            job.LastRun = DateTime.Now;
            job.InProgress = false;
        }, TaskScheduler.FromCurrentSynchronizationContext());;
        job.InProgress = true;
        t.Start();
    }
}

【讨论】:

  • Musefan,如果一个或多个任务还没有完成,MainTick 开始了会怎样?
  • 不多,MainTick 只是保持应该处理的任务列表是最新的。如果一个大任务已经开始运行,那么它将继续不受影响。它至少在完成之前不会再次运行(这就是 InProgress 的用途)
  • 我不太确定的唯一一点是更新 LastRun 和 InProgress 的 ContinueWith 位。不确定job 对象是否仍然可用/相关......恐怕我无法为此设置测试
  • 错误 18 Delegate 'System.Action' 不接受 0 个参数这个错误怎么办? :(
  • See this我更新了我的代码
【解决方案2】:

所以你真正需要的是一个有两个操作的类,它需要能够开始处理你的一个模型,它需要能够结束你的一个模型的处理。将其从列表中分离出来会更容易。

当您开始处理模型时,您需要创建一个 CancellationTokenSource 以与其关联,以便您以后可以停止处理它。在您的情况下,处理它意味着有一个循环,虽然没有取消,但它运行一个操作然后等待一段时间。结束操作就像取消令牌源一样简单。

public class Foo
{
    private ConcurrentDictionary<RepModel, CancellationTokenSource> tokenLookup =
        new ConcurrentDictionary<RepModel, CancellationTokenSource>();
    public async Task Start(RepModel model)
    {
        var cts = new CancellationTokenSource();
        tokenLookup[model] = cts;
        while (!cts.IsCancellationRequested)
        {
            await Task.Run(() => model.DoWork());
            await Task.Delay(TimeSpan.FromMinutes(1));
        }
    }

    public void End(RepModel model)
    {
        CancellationTokenSource cts;
        if (tokenLookup.TryRemove(model, out cts))
            cts.Cancel();
    }
}

【讨论】:

  • 哇,看起来不错,但无法想象它会在我的循环中运行 :(。对我来说太复杂了!
  • @PauloJardim 你不明白什么?您意识到使用它就像在要开始运行模型时调用 Start 和在要停止时调用 End 一样简单。仅此而已。
  • 我的循环在这个示例中的位置?你明白应该是一个无限循环吗?
  • @PauloJardim 循环已经存在。当您调用Start 时,它将循环对模型执行操作,直到调用Stop
  • 但是对于每个模型我都需要传递不同的参数。我有一个
【解决方案3】:

如果您正在使用框架4.0 等,您可以尝试从中受益

Parallel.ForEach

执行一个 foreach 操作,其中迭代可以并行运行。

并行代码可能如下所示:

Parallel.ForEach(listaRep , repModelo => {
     this.coletaFunc(repModelo.EndIp, user, tipoFilial);

});

这将在多个内核上运行(如果可能的话),并且您不需要某些特定的任务调度器,因为您的代码将等待并行循环内的所有并行任务完成。如果满足条件,您可以递归调用相同的函数。

【讨论】:

  • Tigran,我等不及所有任务完成了。请我编辑我的问题
  • @PauloJardim:如果一项任务还没有完成,而另一项任务需要开始,该怎么办?
  • Tigran,应该只跳这个。
  • 但这不是并行引擎本身的问题,但是,这一点,问题出在 Task Scehduler 中,它向队列中注入了一个尚未完成的工作......是任何给定的工作可以唯一标识和查询其执行状态吗?
  • 任务是连接特定规格的硬件并收集一些关于那时的信息,然后保存在数据库中。但是有些机器需要很长时间才能恢复所有信息。但等不及一切都完成了。我尽快需要这些信息
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-15
  • 1970-01-01
  • 2011-05-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多