C# 如何使计时器等待其处理程序中的代码完成执行,以解决多线程问题
这个问题源于最初发布的一个问题的发展。问题在于从多个线程编辑对象的属性 应用程序的上下文如下所示: 我有一个C# 如何使计时器等待其处理程序中的代码完成执行,以解决多线程问题,c#,.net,multithreading,timer,C#,.net,Multithreading,Timer,这个问题源于最初发布的一个问题的发展。问题在于从多个线程编辑对象的属性 应用程序的上下文如下所示: 我有一个System.Timers.Timer对象,它在每个刻度上执行以下编号的项目。我需要一个计时器来完成这项工作,因为我可能希望能够将滴答声间隔从1毫秒更改为60秒 在每个滴答声事件上: 运行后台工作程序从文件[毫秒数](或URL[秒数])读取数据 后台工作人员从文件中读取数据后,引发 事件(如Ticker\u Updated\u Event) 在Ticker\u Updated\u even
System.Timers.Timer
对象,它在每个刻度上执行以下编号的项目。我需要一个计时器来完成这项工作,因为我可能希望能够将滴答声间隔从1毫秒更改为60秒
在每个滴答声事件上:
Ticker\u Updated\u Event
)Ticker\u Updated\u event
的事件处理程序中,运行更新特定属性的对象代码(例如this.MyProperty
)。在这个事件处理程序中执行额外的代码,包括更新GUI的调用[我相信我已经使线程安全(至少我希望如此)]计时器
等待其tick处理程序中的代码完成,然后再执行另一个tick。这是否可以像将布尔表达式附加到勾号处理程序的开始和结束一样简单
编辑1:我希望在代码在其处理程序中完成执行后,计时器立即开始计时。我对我的应用程序以1ms的间隔运行时的性能感兴趣。我希望事件处理程序中的代码能够相当快地执行,并且在使用时间间隔大于200ms的事件处理程序时,不认为这是一个问题
编辑2:我想我没有很清楚地说明我的功能范围。我的程序将在两种状态下运行。在任何运行时,程序将:
下面的内容肯定会跳过勾号,我能想到的唯一其他替代方案是事件队列解决方案。(这更像是
lock(){push();}
并且可能使用另一个线程来执行读取和处理lock(){o=pobornull();}//使用o
class TickHandler
{
static object StaticSyncObject = new object();
static bool IsBusy = false;
private TickHandlerObject myHandler;
static void HandleTimerElapsed(object source, ElapsedEventArgs e)
{
lock(StaticSyncObject)
{
if( IsBusy )
return;
else
IsBusy = true;
}
try {
// ... do some work here ... hopefully often faster than interval.
myHandler.TickerUpdated();
// without any IsBusy checks
// without thread safety (1 thread at a time calls)
// Note: If you're trying to split up threads below
// this, that's up to you. Or diff question.
// ie: file/URL read vs memory/GUI update
} finally {
lock(StaticSyncObject)
{
IsBusy = false;
}
}
}
}
PS-不要对定时器的概念失去信心。一个好的定时器会比平均的while循环有更少的时钟偏差。(我假设时钟偏差在这里很重要,因为你使用的是定时器)
注意:小心地从“数据读取”代码(可在此线程下执行)推送内存的线程安全性,并正确使用控件。调用和/或控件。BeginInvoke
应允许您在不触发任何其他“事件”的情况下完成任务
性能说明:lock
可能需要约50纳秒的低争用时间:。因此,对于毫秒分辨率而言,此代码应该是合适的。注意:系统.Threading.Interlocked
方法可能会将某些操作从50纳秒降低到6纳秒,但在这种情况下,这种差异似乎很小,特别是考虑到复杂性使用联锁(请参阅:)我想我会尝试用我尝试过的解决方案来回答我的问题。如果您发现此解决方案中存在任何潜在缺陷,请告诉我:
此类根据其计时器对象执行代码引发事件:
public class Ticker{
private TickHandlerObject myHandler; // injected dependency
//bw is a background worker
// Handle the Timer tick here:
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
if (myHandler.IsBusy == false) // check here if the handler is still running
{
// do work with background worker, that could take time...
}
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// raise event to be handled by myHanlder object
Updated();
}
}
}
此类实例化ticker并处理其事件
public class TickHandlerObject{
// This class subscribes to the Updated event of myTicker.
private Ticker myTicker;
myTicker.Updated += this.TickerUpdated;
public bool IsBusy{get;set;}
private void TickerUpdated()
{
// thread-safing
this.IsBusy = true;
// do stuff that can take longer than the tick interval
// thread-safing
this.IsBusy = false;
}
}
从您之前的问题和这些更新来看,听起来您可能希望积极地扔掉记号。这是基于以下评论:代码太复杂,无法完全了解多个线程中发生的数据更改,无法进行同步使用共享成员所需的更改
我在上一个问题中发布的伪代码(由ebyrob扩展)是一个合理的选择,假设您没有对我们隐瞒该应用程序在不同的应用程序域中多次运行
如果您想尝试将某些内容填充到现有解决方案中,请尝试以下操作:
class UpdateThrottler
{
static object lockObject = new object();
static volatile bool isBusyFlag = false;
static bool CanAcquire()
{
if (!isBusyFlag)
lock(lockObject)
if (!isBusyFlag) //could have changed by the time we acquired lock
return (isBusyFlag = true);
return false;
}
static void Release()
{
lock(lockObject)
isBusyFlag = false;
}
}
然后在代码中,您可以做一些简单的事情:
Tick_Handler(...)
{
if (UpdateThrottler.CanAcquire())
{
try
{
//Do your work
}
finally
{
UpdateThrottler.Release();
}
}
}
这有一定的灵活性,因为如果出于某种原因,您不确定是否能在Tick处理程序的末尾完成工作,您可以从另一个位置调用Release。这可能是因为您使用了后台工作程序,其回调用于完成剩余的工作或其他任何工作。我不完全确定这是一种好的做法,但没有花费足够的时间是时候全面了解您的应用程序了,我不确定还能做些什么
在这一点上,我们正在危险地接近复制对象
编辑:经过一番讨论,我意识到使用volatile标记并没有什么坏处,而且可以缓解某些潜在的边缘情况。如果你在每个刻度上执行,为什么一定要使用计时器?假设你的计时器每5秒刻度一次,一次操作需要2秒。计时器下一次刻度是在3秒后,还是在3秒后5秒?如果勾号事件需要9秒,它是在2秒后还是在5秒后再次勾号?(或我没有列出的第三个选项?)@tmckown在执行“下一步”之前等待一段时间操作。我想如果你要更新UI,就需要使用计时器。如果你想让它立即运行,那么你真的不需要计时器。只需要完成上一个事件就可以启动