如何在应用程序中添加暂停/恢复功能
本文关键字:暂停 恢复 功能 添加 应用程序 | 更新日期: 2023-09-27 18:10:48
我正在编写一个应用程序,其中大部分工作是由后台线程完成的(10 - 500线程)。
我想添加暂停/继续功能。
以前,你可以用Thread来做。Suspend和Thread.Resume。但是这些功能现在被认为已经过时了。
有没有什么东西可以让我同样轻松地做同样的事情?
我正在用c#编写软件
我用c#写了一个高性能的爬虫,我可以很权威地说,显式地管理几十个或几百个线程并不是最好的方法。这是可以做到的(我做到了),但它是极端痛苦的。
话虽如此……
如果你的应用程序是按照我的想法编写的,那么每个线程都是这样做的:
while (!Shutdown)
{
// get next url to crawl from somewhere
// download the data from that url
// do something with the data
}
在下载之间暂停线程非常简单。我建议创建两个ManualResetEvent
实例:一个用于继续,一个用于关闭。这些是static
,以便所有爬虫线程都可以访问它们:
static ManualResetEvent ShutdownEvent = new ManualResetEvent(false);
static ManualResetEvent ContinueEvent = new ManualResetEvent(true);
然后,每个线程在循环中使用WaitAny
:
WaitHandle[] handles = new WaitHandle[] { ShutdownEvent, ContinueEvent };
while (true)
{
int handle = WaitHandle.WaitAny(handles); // wait for one of the events
if (handle == -1 || handle >= handles.Length)
{
throw new ApplicationException();
}
if (handles[handle] = ShutdownEvent)
break; // shutdown was signaled
if (handles[handle] == ContinueEvent)
{
// download the next page and do something with the data
}
}
注意,在定义handles
数组时,我首先指定了ShutdownEvent
。原因是,如果多个项目被标记,WaitAny
返回与被标记对象对应的最低索引。如果数组是按其他顺序填充的,那么不先暂停就无法关闭。
现在,如果您想关闭线程,调用ShutdownEvent.Set
。如果你想线程暂停,调用ContinueEvent.Reset
;如果你想线程恢复,调用ContinueEvent.Set
。
在下载过程中暂停有点困难。这是可能的,但问题是,如果您暂停太长时间,服务器可能会超时。然后,您必须从头开始重新开始下载,或者,如果服务器和您的代码支持,则从您停止的位置重新开始下载。这两种选择都很痛苦,所以我不建议在下载过程中暂停。
你的应用是做什么的?
500个线程太多了——仅仅是堆栈就占用了1/2 GB的已提交内存。然后你有所有的上下文切换。
很好,你想摆脱Suspend
和Resume
调用,但我建议你先看看你的架构-你能转移到APM方法(BeginXXX/EndXXX)吗?
我要说的是:你的应用程序做什么并不清楚;有许多简单的方法可以使用线程池线程,如TPL、后台工作线程等。
但是,如果您有创建的线程(不是线程池),并且希望它们通信,则使用Monitor。等待和监控。带布尔阻塞条件的脉冲。
例句:
bool _isPaused;
void DoWork()
{
while (true)
{
lock (_locker)
{
while (_isPaused) Monitor.Wait(_locker);
// your worker code here
}
}
}
//
void UnPause()
{
lock (_locker)
{
_isPaused=false;
Monitor.PulseAll(_locker);
}
}
不完全是。暂停/恢复真的很简单,工作很好,直到他们崩溃你的应用程序,例如。通过挂起内存管理器或文件系统被锁定的线程:(
通常,稍微复杂一点的方法是在线程中找到一个可以等待的地方。在你的情况下,我猜大多数线程通常在一些IO调用上被阻塞,因此被'挂起',所以强制'挂起'的好地方是在读取之后,为了捕捉那些读取返回的线程。你可以通过检查全局布尔'isRunning'标志来执行实际的挂起,@Andrey建议,如果需要挂起,阻塞全局ManualResetEvent。要挂起,先清除事件,然后清除标志。要继续,先设置标志,再设置事件。
如果使用全局变量让你感到恶心,你可以在actor中传递一些类的通用实例,其中包含flag, event和'suspend(), 'resume()'和'checkForSuspend()'方法。
祝好,马丁