总结:我有一个Web应用程序,可以在业务对象上执行工作流,有时需要在步骤之间有意等待几秒或几分钟。我希望(也许通过Rx.NET)改进这些工作流程的执行,这样我就不会耗尽ThreadPool,并在系统负载很重时使网站无响应。
工作流程的一个非常简化的版本是:
如果系统A关闭,我的应用程序会等待并稍后重试。等待时间是在GMail不断升级的重试延迟之后建模的:等待1秒,每次后续重试加倍(最多1小时)。该应用程序痴迷地将状态保存到数据库,因此如果整个应用程序爆炸,当它重新启动时,它将恢复它停止的所有工作流程。
目前(请保持温和)工作流程中的每个步骤都是通过调用ThreadPool.QueueUserWorkItem来排队调用Thread.Sleep的方法来执行,如果需要进行上述重试延迟,则实际执行该步骤。
如果系统运行良好(没有错误),它可以轻松处理我们抛出的所有流量,并且ThreadPool可以很好地管理所有这些工作流实例的并行执行。但是如果系统B停机一段时间,重试计数会因此延迟增长,很快ThreadPool就会充满所有正在使用的线程,导致网站对新请求没有响应。
基本上我想将所有这些待处理的工作流抛出到按顺序排列的队列中(上次执行时间+所需的重试延迟)。尽管阅读了很多关于Rx并且很兴奋的事情,但我从来没有机会使用它,但似乎它可能是一种有用的方法来处理这个问题。如果Rx能够在他们准备开火的时候神奇地管理吐出这些物体,那么它似乎就是
对Rx新手的任何指导都会非常感激,即使只是为了解释为什么这对Rx来说实际上不是一个好的用例。
答案 0 :(得分:4)
在这种情况下,我可能会坚持使用您当前的解决方案,因为这一点:
该应用程序痴迷地将状态保存到数据库中,因此如果整个应用程序爆炸,当它重新启动时,它将恢复所有停止的工作流程。
启动时通过反序列化“恢复”管道(即x.Where().Select().Timeout().Bla()
)非常棘手。
如果你没有尝试对整个流程进行建模,只是交易位(即从A加载,发送到B),很难为你提供更详细的解决方案而没有更多信息,它可能与Rx实际上相当不错。
无论如何,解决线程池耗尽的方法是通过System.Threading.Timer类,它告诉线程池在排队新项之前等待超时。
答案 1 :(得分:2)
你肯定要适应:
public IDisposable StartProcess<T>(Action<T> load, Action<T> post) where T : new()
{
return StartProcess(TimeSpan.FromSeconds(1), new T())
.Do(load)
.Subscribe(post);
}
private IObservable<long> StartProcess<T>(TimeSpan span, T obj) where T : new()
{
Observable
.Interval(span)
.OnErrorResumeNext(Observable.Defer(() => StartProcess(IncreaseSpan(span), obj)))
.Concat(Observable.Defer(() => StartProcess(TimeSpan.FromSeconds(1), new T())));
}
private TimeSpan IncreaseSpan(TimeSpan span)
{
return TimeSpan.FromSeconds(span.TotalSeconds < 1800? span.TotalSeconds * 2 : 3600);
}
现在我更倾向于加载实例化并填充对象而不是明确地执行它,因为函数式编程会阻止可变性,您可能希望load
实际转到数据库并恢复您提到的状态。 / p>
我不确定你是否想要保留状态对象以防post
或load
的调用崩溃而你需要适应,因为目前它会保留状态{是否{如果load
崩溃,那么{1}}或post
会崩溃,并且会在没有新状态的情况下再次致电load
。
我没有测试代码,但Rx适合你想要做的事情。
答案 2 :(得分:1)
在Rx论坛上查看此帖子。对于您想要解决的问题,非常方便的运算符:http://social.msdn.microsoft.com/Forums/en-US/rx/thread/af43b14e-fb00-42d4-8fb1-5c45862f7796/
Rx是处理这类(特别是)问题的好方法,因为你可以拥有异步函数/ observables并将通用运算符应用于所描述的Retry运算符。