我有一个 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 首先,我想确保当其中一个有时运行很长时,它们不会相互束缚。
例如。如果任务一有时需要 20 秒才能运行,我想防止出现重复的计时器,而前一个计时器可能仍在运行,实际上所有计时器都是如此。例如。如果 t2 的运行时间比平时稍长,则不要启动另一个。我读过一些有关 if (Monitor.TryEnter(lockObject))
的内容,这是处理该要求的最佳方法吗?
2 其次,如果它们都访问相同的资源(在我的例子中是 EF 上下文),则 t3 已经在使用它,并且 t4 会尝试这样做。有没有办法让计时器等待另一个计时器完成?
3 最后有没有办法可以监控这些计时器/回调?我想提供一个 UI 来查看它作为 Windows 服务运行时的状态。我的最终目标是提供一个用户界面,用户可以查看任务是否正在运行,如果任务没有运行一段时间,则根据需要触发它。但同时,不要在运行时创建重复项。
我想知道我是否应该将这些作为单独的问题提出,但它们似乎与彼此的决定如此紧密地交织在一起。
最佳答案
如果您必须确保每个线程没有任何重叠,您可以使用Timer.Change(int, int)
方法在回调开始时停止执行,然后在回调结束时恢复执行。您还可以为每个线程使用 ManualResetEvent
施展一些魔法,但这会变得困惑。
我不喜欢线程定时器,并尽可能避免使用它们。如果您可以牺牲“每个线程必须在n秒后运行”,那就这样做。使用带有取消标记的任务,它将解决您的重叠问题。例如:
A.
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
。考虑到上面的例子,它看起来像这样:
B.
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 上下文的每个线程中执行此操作。请再次记住,由于复杂的锁定
场景会带来认知负担,维护会变得很烦人。
我最近开发了一个应用程序,其中需要多个线程来访问相同的 EF 上下文。正如我上面提到的,锁定太多(并且存在性能要求),因此我设计了一种解决方案,其中每个线程将其对象添加到公共(public)队列中,并且单独的线程除了从队列中提取数据之外什么也不做调用 EF。这样一来,EF 上下文只能由一个线程访问。问题解决了。根据上面的示例,情况如下:
C.
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
。我不知道它是否是正确使用的对象,其他人可能可以对此发表评论。创建一个 Synchronization 对象,将其传递给您的线程,让它们使用必要的数据更新它,并将其发布到您的 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());
}
}
}
再说一遍,我就是这样做的,我不知道这是否是正确的方法。
关于c# - 多个计时器/回调——防止重复并监控它们的最佳方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35272914/