gpt4 book ai didi

c# - 多个计时器/回调——防止重复和监控它们的最佳方法

转载 作者:行者123 更新时间:2023-11-30 23:29:44 25 4
gpt4 key购买 nike

我有一个 c# 控制台,我已将其制作成 Windows 服务,我希望它能够可靠且持续地运行。

  1. 我想防止重叠同一计时器再次触发
  2. 我想防止不同的定时器同时尝试使用相同的资源
  3. 我希望能够监控计时器并与之互动。

它有几个方面。每个都非常定期地运行。我以前读过 TaskScheduler vs Windows Service 运行这种东西,并选择了这种方法,因为有些东西几乎一直在运行。

  • 任务类型 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 最后,有什么方法可以监控这些计时器/回调吗?当我将它作为 Windows 服务运行时,我想提供一个 UI 来查看它的状态。我的最终目标是提供一个 UI,用户可以看到任务是否正在运行,如果没有,则在一段时间内未设置为运行时按需触发它。但同时,不要在运行时创建副本。

我想知道我是否应该将这些作为单独的问题提出,但它们似乎与彼此的决定交织在一起。

最佳答案

如果你必须确保每个线程没有任何重叠,你可以使用 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 上下文的每个线程中执行此操作。请记住,由于复杂的 lock 场景会带来认知负担,维护工作会再次变得烦人。

我最近开发了一个应用程序,其中我需要多个线程来访问同一个 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/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com