多个计时器/回调 — 防止重复和监控重复的最佳方法
本文关键字:监控 方法 最佳 计时器 回调 | 更新日期: 2023-09-27 18:33:45
我有一个c#控制台,我已经把它变成了一个Windows服务,我想可靠和持续地运行它。
- 我想防止同一计时器再次触发重叠
- 我想防止不同的计时器尝试同时使用相同的资源
- 我希望能够监控计时器并与之交互。
它有几个方面。 每个都非常有规律地运行。 我以前读过关于TaskScheduler与Windows服务运行这种东西的文章,并选择了这种方法,因为某些东西几乎一直在运行。
- 任务类型1
- 任务类型2
- 任务类型3
- 任务类型4
都有自己的回调,类似于这个简化版本:
class Program
{
static PollingService _service;
static void Main()
{
_service = new PollingService();
TimerCallback tc1 = _service.TaskType1;
TimerCallback tc2 = _service.TaskType2;
TimerCallback tc3 = _service.TaskType3A;
TimerCallback tc4 = _service.TaskType3B;
Timer t1 = new Timer(tc1, null, 1000, 5000);
Timer t2 = new Timer(tc2, null, 2000, 8000);
Timer t3 = new Timer(tc3, null, 3000, 11000);
Timer t4 = new Timer(tc4, null, 4000, 13000);
Console.WriteLine("Press Q to quit");
while (Console.ReadKey(true).KeyChar != 'q')
{
}
}
}
class PollingService
{
public void TaskType1(object state)
{
for (int i = 1; i <= 10; i++)
{
Console.WriteLine($"TaskOne numbering {i}");
Thread.Sleep(100);
}
}
public void TaskType2(object state)
{
for (int i = 10; i <= 100; i++)
{
Console.WriteLine($"TaskTwo numbering {i}");
Thread.Sleep(100);
}
}
public void TaskType3A(object state)
{
Increment(200000000);
}
public void TaskType3B(object state)
{
Increment(40000);
}
private void Increment(int startNumber)
{
for (int i = startNumber; i <= startNumber + 1000; i++)
{
Console.WriteLine($"Private {startNumber} numbering {i}");
Thread.Sleep(5);
}
}
}
1 首先,我想确保当一个有时跑得很长时,这些不会相互捆绑。
例如。如果任务 1 有时需要 20 秒才能运行,我想防止重复的计时器,而前一个计时器可能仍在运行,事实上所有计时器都是一样的。 例如。如果 T2 的运行时间比平时长一点,则不要启动另一个。 我已经读过一些关于if (Monitor.TryEnter(lockObject))
,这是处理该要求的最佳方法吗?
2 其次,如果它们都访问相同的资源(在我的例子中是 EF 上下文(,则 t3 已经在使用它,并且 t4 尝试这样做。 有没有办法让计时器等到另一个完成?
3 最后,有没有办法监控这些计时器/回调? 我想提供一个 UI 来查看它作为 Windows 服务运行时的状态。 我的最终目标是提供一个 UI,用户可以查看任务是否正在运行,如果没有,则在一段时间内未设置为运行时按需触发它。 但同时,不要在运行时创建副本。
我想知道我是否应该把这些问题作为单独的问题来问,但它们似乎与彼此的决定交织在一起。
如果必须确保每个线程没有任何重叠,则可以使用 Timer.Change(int, int)
方法在回调开始时停止执行,然后在回调结束时恢复执行。你也可以为每个线程做一些魔术,用一个ManualResetEvent
,但它会变得混乱。
我不喜欢线程计时器,并尽可能避免使用它们。如果你可以牺牲"每个线程必须在n秒后运行",那就去做吧。请改用带有取消令牌的任务,它将解决您的重叠问题。例如:
一个。
public class Foo
{
private CancellationTokenSource _cts;
//In case you care about what tasks you have.
private List< Task > _tasks;
public Foo()
{
this._cts = new CancellationTokenSource();
this._tasks.Add(Task.Factory.StartNew(this.Method1, this._cts.Token));
this._tasks.Add(Task.Factory.StartNew(this.Method2, this._cts.Token));
this._tasks.Add(Task.Factory.StartNew(this.Method3, this._cts.Token));
this._tasks.Add(Task.Factory.StartNew(this.Method4, this._cts.Token));
}
private void Method1(object state)
{
var token = (CancellationToken) state;
while ( !token.IsCancellationRequested )
{
//do stuff
}
}
private void Method2(object state)
{
var token = (CancellationToken)state;
while (!token.IsCancellationRequested)
{
//do stuff
}
}
private void Method3(object state)
{
var token = (CancellationToken)state;
while (!token.IsCancellationRequested)
{
//do stuff
}
}
private void Method4(object state)
{
var token = (CancellationToken)state;
while (!token.IsCancellationRequested)
{
//do stuff
}
}
public void StopExecution()
{
this._cts.Cancel();
}
}
如果一次由多个线程使用,EF 上下文将引发异常。有一种方法可以同步它,使用 lock
.它看起来像这样,给定上面的示例:
二.
public class Foo
{
private object _efLock;
public Foo()
{
this._efLock = new object();
}
.
.
.
private void MethodX(object state)
{
var token = (CancellationToken)state;
while (!token.IsCancellationRequested)
{
lock(this._efLock)
{
using(.......
}
}
}
}
必须在访问 EF 上下文的每个线程中执行此操作。请记住,同样,由于复杂的lock
场景带来的认知负荷,维护变得烦人。
我最近开发了一个应用程序,其中需要多个线程来访问相同的 EF 上下文。正如我上面提到的,锁定必须太多(并且有性能要求(,因此我设计了一个解决方案,其中每个线程将其对象添加到公共队列,而单独的线程除了从队列中提取数据并调用 EF 外什么都不做。这样,EF 上下文只能由一个线程访问。问题解决了。下面是给定上述示例的外观:
三.
public class Foo
{
private struct InternalEFData
{
public int SomeProperty;
}
private CancellationTokenSource _dataCreatorCts;
private CancellationTokenSource _efCts;
//In case you care about what tasks you have.
private List< Task > _tasks;
private Task _entityFrameworkTask;
private ConcurrentBag< InternalEFData > _efData;
public Foo()
{
this._efData = new ConcurrentBag< InternalEFData >();
this._dataCreatorCts = new CancellationTokenSource();
this._efCts = new CancellationTokenSource();
this._entityFrameworkTask = Task.Factory.StartNew(this.ProcessEFData, this._efCts.Token);
this._tasks.Add(Task.Factory.StartNew(this.Method1, this._dataCreatorCts.Token));
this._tasks.Add(Task.Factory.StartNew(this.Method2, this._dataCreatorCts.Token));
.
.
.
}
private void ProcessEFData(object state)
{
var token = (CancellationToken) state;
while ( !token.IsCancellationRequested )
{
InternalEFData item;
if (this._efData.TryTake(out item))
{
using ( var efContext = new MyDbContext() )
{
//Do processing.
}
}
}
}
private void Method1(object state)
{
var token = (CancellationToken) state;
while ( !token.IsCancellationRequested )
{
//Get data from whatever source
this._efData.Add(new InternalEFData());
}
}
private void Method2(object state)
{
var token = (CancellationToken) state;
while ( !token.IsCancellationRequested )
{
//Get data from whatever source
this._efData.Add(new InternalEFData());
}
}
public void StopExecution()
{
this._dataCreatorCts.Cancel();
this._efCts.Cancel();
}
}
当涉及到从执行线程读取数据时,我通常使用 SynchronizationContext
.我不知道它是否是正确的对象,其他人可能会对此发表评论。创建一个同步对象,将其传递给线程,并让线程使用必要的数据对其进行更新,并将其发布到 UI/控制台线程:
D.
public struct SyncObject
{
public int SomeField;
}
public delegate void SyncHandler(SyncObject s);
public class Synchronizer
{
public event SyncHandler OnSynchronization;
private SynchronizationContext _context;
public Synchronizer()
{
this._context = new SynchronizationContext();
}
public void PostUpdate(SyncObject o)
{
var handleNullRefs = this.OnSynchronization;
if ( handleNullRefs != null )
{
this._context.Post(state => handleNullRefs((SyncObject)state), o);
}
}
}
public class Foo
{
private Synchronizer _sync;
public Foo(Synchronizer s)
{
this._sync = s;
}
private void Method1(object state)
{
var token = (CancellationToken) state;
while ( !token.IsCancellationRequested )
{
//do things
this._sync.PostUpdate(new SyncObject());
}
}
}
再说一次,这就是我的做法,我不知道这是否是正确的方法。
- 基本上,是的,或者
AutoResetEvent
- 您可以停止它,等待资源释放,然后重新启动它
- 保留与计时器关联的状态列表,并从计时器更新这些状态(设置为启动时运行,设置为完成后等待,或类似这些行(
1:可能最好的办法是不在计时器中做任何事情,而是统计一个任务 - 如果有的话,当设置或未设置标志时。寻找联锁(类(了解如何在不锁定的情况下实现它。
2:监视器。但说真的,为什么它们共享一个 EF 连接?
3:当然。创建性能计数器。监视它们。该 API 在窗口中存在了很多年。