0

为什么我真的需要这个:(底部问题的简单描述,在edit3下)

让我们想象一下,我尝试为 twitter 创建应用程序。它有尽可能多的帐户,我现在或以后要添加(例如 10 个)。因此,对于每个帐户,我们都有一个选项:

  1. 来自 rss 的推文;
  2. 跟随人;
  3. 转发任何推文。

当我们谈论一个帐户时,每个任务都应该在后台工作并在其他任务中同步。对于其中一个帐户,一次(请求太多)不会有推特和关注。

好的,这意味着,我一次只能为每个帐户执行一项任务。这是否意味着,我必须为这个时刻帐户中的每个活动组织一个线程?但是线程是什么,告诉每个帐户下一步该做什么(填满队列)?一个账号想推特,另一个只是转发和关注人:

  1. (thread 1) Account A:整天想从RSS/文件推文;
  2. (线程2)账号B:想每30秒转发一次,每15分钟关注人一次;
  3. (线程 3):应该填满“帐户 A”的队列(制作 twite,制作 twite,制作 twite);
  4. (线程 4):应该填满“帐户 B”的队列(进行转发,进行转发(在 15 分钟内,每 30 秒)...关注人,进行转发...);

在这种情况下,当每个帐户的一个线程刚刚开始工作(队列中的内容->他做了什么)而另一个线程刚刚填满队列时,我可以在我的功能中添加手动任务(现在,我所有的账户,转发这个 twite或跟随这个人),-在每个队列中,我将添加一个新任务,然后每个线程尽快执行该任务。

所以,我的目标(目标)是创建一个应用程序,每个帐户都应该在其中工作,我可以选择告诉他们下一步该做什么(更改他们的任务优先级)。

我想问你,我对每个 acc 2 个线程,关于队列(也许它应该是一个全局队列,线程使用帐户作为标记(分隔符)来执行任务)是否正确,也许你可以建议我关于我的任务的一些事情。

编辑: 那里最大的问题是,如果我创建 20 个帐户,但只有 5 个线程(限制)- 所以它不应该是 1 个 acc-> 2 线程,应该是 1 个线程来填充队列(看起来像全球),以及其他用于制作工作 - 但他们可以为一个帐户制作作品(一个线程制作 twite,其他线程开始关注用户),这让我陷入困境, - 不明白,我应该如何组织所有这个线程。

Edit2:还有一个问题:执行时间。我是否应该检查每个线程时间,何时执行任务(计划下一个小时的任务)(现在 23:55,这个任务将在 23:59 执行,-下一步是什么?跳过?睡觉?)。

如果我不应该检查时间并且在队列中只添加任务,那已经准备好执行,我又遇到了一个帐户一个任务的问题(设置添加 twite 15 秒延迟,添加转发 20 秒延迟,跟随人们 60 秒延迟) => 每分钟我将有 3 个任务在队列中,但应该一个一个地执行(我有 100 个帐户,但只有 5 个工作线程(它们应该相互同步,什么帐户任务现在执行其他线程?)) .

Edit3(让它变得简单): 好吧,让我们忘记推特吧。我要编写 Windows 桌面应用程序,它应该在控制台中写一些东西(让它成为“来自 Bob 的你好”、“来自 Bob 的嗨”、“来自 Bob 的再见”)。

我可以在我的程序中添加很多名字(Bob、Jack、John、Dave、Scott)并检查这些名字应该写什么短语:

  1. Bob 每 15 秒只写一次“来自 Bob 的你好”;
  2. 杰克每 1 分钟写一次“来自杰克的你好”,每 5 秒写一次“来自杰克的再见”;
  3. John 写 'hello', 'hi', 'bye' + from 'John', 5 seconds, 10 seconds, 15 seconds;
  4. 等(假设为 20 个名称);

我希望,这样好吗?

所以,现在,当我启动我的应用程序时,这 20 个名字中的每一个都应该在控制台中写下他们的短语,但是要在他们的时间和所有线程中进行(让限制它有 10 个工作线程) - 应该是每个名字一个任务(如果一个线程现在写 'hello from Jack',其他线程不应该写 'bye from Jack',即使这个任务应该在 01:42:00 执行。只有当 Jack 完成一项任务时,其他任务才开始) .

(+) 另外,我应该有一个选项,在他们最接近的任务完成后,为这个名字中的某个人添加我想要的任何短语。

我希望,我会找到你的帮助。

编辑4:所以:在此处输入图像描述

谢谢帮助,现在我知道我需要什么以及如何组织我的应用程序。

最终实现:

我已经使用自己的代码完成了我的任务,对我来说,解决方案看起来是最好的。

在此处输入图像描述

因此,当我为每个帐户生成队列并将它们放入一个全局应用程序任务队列时,我将其传递给 TimerCallBack 函数,该函数检查队列中是否有任何准备好执行的任务(但仅检查 event_elements[0] ),- 如果是这样,设置 isactive = true (对于个人队列,防止执行下一个事件,在前一个完成之前),并放置这个队列,应该在迭代后执行每个第一个事件。

然后,将其作为集合传递给 Parallel.ForEach:

 Parallel.ForEach(readyToExec, new ParallelOptions { MaxDegreeOfParallelism = 5 }, eq =>
            {
                lock (eq)
                {
                    QueueElement exec_elem = eq.elements[0];
                    Console.WriteLine(exec_elem.timeToExecute + ": " + eq.account.Name + " " + exec_elem.EventType);
                    exec_elem.timeToExecute = DateTime.Now + exec_elem.timeDelay;
                    eq.timeLastExecute = DateTime.Now;
                    eq.elements.Sort();
                    eq.isactive = false;
                }
            });

顺便说一句,在添加要执行的事件之前添加了检查:

        foreach (var equeue in queue)
        {
            if (!equeue.isactive && equeue.elements[0].readyToExecute)
            {
                if (DateTime.Now > equeue.timeLastExecute + TimeSpan.FromSeconds(5))
                {
                    equeue.isactive = true;
                    readyToExec.Add(equeue);
                }
                else
                {
                    equeue.elements[0].timeToExecute = DateTime.Now + TimeSpan.FromSeconds(5);
                }
            }
        }

以手动延迟执行一个帐户的事件(例如,如果在 00:00:00 写入“Hello”,则直到 00:00:05 才能开始执行下一个事件,即使此执行时间为 00 :00:02)。

所以,它对我很有用;)

4

1 回答 1

0

我的第一个倾向是使用优先级队列和Timer。这就是我的做法。

首先,创建一个全局Stopwatch来维护应用程序时间。您不希望夏令时或其他时间更改打乱您的时间安排,因为这可能会导致您在事件之间等待太久或使事件发生得太快。所以,在某个地方你有:

static Stopwatch ApplicationTime = Stopwatch.StartNew();

您还有一个Account类,其中包含有关 Twitter 帐户的信息,最重要的是,该帐户最后一次执行操作的时间:

class Account
{
    // information about the account goes here

    public TimeSpan LastEventTime { get; set; }
}

以及定义将要采取的动作的事件类。

class AccountEvent
{
    public Account acct { get; private set; }
    public TimeSpan dueTime { get; set; }
    public ActionType action { get; private set; } // follow, re-tweet, etc.

    // constructor, etc.
}

最后,一个以事件时间为键的优先级队列:

PriorityQueue<TimeSpan, AccountEvent> EventQueue = new Priority<TimeSpan, AccountEvent>();

不幸的是,.NET 运行时库中没有内置优先级队列类。如果你做一个谷歌搜索,你可以找到几个。我多年前写的一篇文章位于http://www.devsource.com/c/a/Languages/A-Priority-Queue-Implementation-in-C/您可以在http://mischel.com/pubs/priqueue.zip获得最新的源代码。请注意,我的优先级队列不是并发的;你必须在它周围包裹一个并发层。出于您的目的,lock在以任何方式访问队列之前进行简单的操作就足够了。

现在,如何使用所有这些东西:

假设您想每 30 分钟检查一次帐户。你会像这样创建一个新的AccountEvent

var ev = new AccountEvent(userAccount, ApplicationTime.Elapsed.AddMinutes(30), ActionType.Follow);
EventQueue.Enqueue(ev.dueTime, ev);

现在,您需要一些东西来从队列中删除项目并处理它们。这就是计时器进来的地方:

Timer eventTimer = new System.Threading.Timer(QueueProc, null, TimeSpan.FromSeconds(5), TimeSpan.FromMilliseconds(-1));

这将创建一个将在五秒内触发的计时器。它是一次性计时器而不是周期性计时器,这可以防止我们获得重叠的计时器滴答声。

现在,您的计时器处理程序检查队列中需要处理的事件:

void QueueProc(object state)
{
    while (eventQueue.Count > 0 && eventQueue.Peek().dueTime < ApplicationTime.Elapsed)
    {
        AccountEvent ev = eventQueue.Dequeue();
        // process item. See comments below.
    }
    // reset the timer so that it will fire in five seconds
    eventTimer.Change(TimeSpan.FromSeconds(5), TimeSpan.FromMilliseconds(-1));
}

因为一个帐户可以有多个事件,所以事件处理有点复杂,但并不过分。您必须将当前时间与帐户的上次活动时间进行比较。如果最后一个事件太近,则在事件时间上添加一些时间,并将其与新时间一起放回队列中。

要处理项目,您可以触发异步 Web 请求(我的建议)、使用BackgroundWorker或触发 TPL Task。关键是当异步请求完成时,完成回调做的最后一件事是更新下一个事件的时间并将该事件放回队列中。

我在这里忽略了一些细节,但我认为您可以收集基本思想:至少足以开始。

于 2012-11-17T01:28:04.537 回答