文章目录
在开发中,会遇到并行处理的需求。
有时只需要使用task(底层是创建个线程)来处理一下就好了。而有时则在并行处理的基础上还有时间的要求,较常见的就是每隔一定时间处理一次。当然,这用task肯定可以实现,但是时间这块得自己控制,无疑增加了工作量和不确定性。
.NET提供了叫做定时器(timer,也叫计时器)的类,它在并行处理的基础上,带了时间参数的设置,可以满足这一需求。
其实本文标题与其叫C#定时器,不如叫.NET定时器好些。因为这边介绍的定时器是.NET中的东西,不只针对C#(不过调用形式是C#的),.NET平台下的其他语言也可以用。不过.NET下的语言,用C#的可能相对多一些,所以很多时候.NET的东西也被叫做C#的东西,当然这种叫法不是很规范。
.NET提供了两种定时器用于多线程环境:
⚠注意:
一些.NET实现下还有其它类型的定时器:
- System.Windows.Forms.Timer,从名字中就能看出来,它是WinForms的定期触发事件的组件,是为单线程环境设计的。
- System.Web.UI.Timer,这是一个ASP.NET组件,以固定间隔执行异步或同步的网页回发。
- System.Windows.Threading.DispatcherTimer,集成到Dispatcher队列中的定时器,它会按照指定的时间间隔和指定的优先级进行处理。
System.Threading.Timer类能以指定间隔调用委托(连续或单次)。该委托在ThreadPool线程中执行。
创建System.Threading.Timer对象时,你需要指定一个TimerCallback委托来定义回调方法、一个传递给回调函数的可选state对象、以及首次调用回调函数之前的延迟时间和连续回调调用的时间间隔。要取消一个挂起(pending)的定时器,可以调用Timer.Dispose方法。
下面代码示例创建了一个定时器,在创建一秒后首次调用委托,之后每两秒调用一次。示例中的state对象用于计算调用委托的次数。当委托被调用10次后,计时器停止。
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
private static Timer timer;
static void Main(string[] args)
{
var timerState = new TimerState { Counter = 0 };
timer = new Timer(
callback: new TimerCallback(TimerTask),
state: timerState,
dueTime: 1000,
period: 2000);
while (timerState.Counter <= 10)
{
Task.Delay(1000).Wait();
}
timer.Dispose();
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}: done.");
}
private static void TimerTask(object timerState)
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}: starting a new callback.");
var state = timerState as TimerState;
Interlocked.Increment(ref state.Counter);
}
class TimerState
{
public int Counter;
}
}
使用TimerCallback委托来指定希望Timer执行的方法。TimerCallback委托的签名如下:
void TimerCallback(Object state)
定时器委托是在定时器被构造之后(即new了之后)指定的,且无法更改。该方法不会在创建定时器的线程上执行;它是在系统提供的ThreadPool线程上执行。
Timer类有着与系统时钟相同的分辨率。这意味着,如果周期小于系统时钟的分辨率,TimerCallback委托将按照系统时钟的分辨率定义的时间间隔执行,在Win7和Win8系统上大约是15ms。可以使用Change方法更改到期时间和周期,或禁用定时器。
⚠注意:
只要使用Timer,就必须保持对它的引用。
与其它托管对象一样,当没有对Timer的引用时,Timer也会受到GC(垃圾回收器)的影响。即使Timer仍然处于活动状态,也不会阻止它被回收。
使用的系统时钟与GetTickCount使用的时钟相同,不受timeBeginPeriod和timeEndPeriod改变的影响。
当不再需要定时器时,使用Dispose方法释放定时器所持有的资源。注意,回调函数可能在Dispose()方法重载被调用之后才发生,因为定时器队列回调函数由线程池线程执行。可以使用Dispose(waitHandle)方法重载来等待,直到所有回调完成。
该定时器执行的回调方法是可重入的(多个线程同时执行,第一个线程还没执行完,第二个线程又进去执行了),因为它是在ThreadPool线程上调用的。如果定时器间隔小于执行回调所需的时间,或者如果所有线程池线程都在使用并且多次排队,则可以在两个线程池线程上同时执行回调。
⚠注意:
System.Threading.Timer是一个简单的轻量级定时器,它使用回调方法,由线程池线程提供服务。不建议在WinForms中使用,因为它的回调函数不会在UI线程上发生。System.Windows.Forms.Timer更适用于WinForms。对于基于服务器的定时器功能,可以考虑使用System.Timers.Timer,它会引发事件并具有额外功能。
另一个用于多线程环境的定时器是System.Timers.Timer,默认情况下,它会在ThreadPool线程中引发一个事件。
当创建System.Timers.Timer对象时,可以指定引发事件的时间间隔。使用Enabled属性来指定定时器是否引发事件。如果要指定只引发一次Elapsed事件,将AutoReset设置为false。AutoReset属性的默认值为true,意味着在interval属性定义的时间间隔内会定时引发Elapsed事件。
Timer组件是一个基于服务器的定时器,它会在经过Interval属性设置的毫秒数之后引发一个Elapsed事件。使用AutoReset属性配置Timer对象,使其只引发一次或重复引发事件。通常,Timer对象声明在类层级,以便你需要它时,它就在作用域中。
// 声明在类层级大概是这个意思?
// 不是定义在局部的,而是整个类的成员变量(字段)
// 这样你才类中任意地方都可以去操作它
class A
{
Timer _timer;
}
然后可以处理它的Elapsed事件来进行常规处理。例如,假设你有一个服务器,它必须每周7天、每天24小时运行。你可以创建一个使用Timer对象的服务来定期检查服务器,并确保系统已启动并运行。如果系统没有响应,服务可以尝试重新启动服务器并通知管理员。
⚠注意:
该Timer类并不适用所有的.NET实现和版本,例如,.NET Standard 1.6以及更低版本。在这些情况下,你可以使用System.Threading.Timer类。
从这句话中有种System.Timers.Timer的使用优先级比System.Threading.Timer高的感觉。
该Timer类实现了IDisposable接口。当你使用完该类后,应该销毁它。要直接销毁该类,在try/catch块中调用它的dispose方法。间接销毁,可以使用using。
基于服务器的System.Timers.Timer类是为多线程环境中的工作线程而设计的。服务器定时器可以在线程之间移动来处理引发的Elapsed事件,在引发事件及时性上比Windows定时器更精确。
System.Timers.Timer组件根据Interval属性的值(以毫秒为单位)引发Elapsed事件。通过处理此事件来执行所需的处理过程。例如,假设你有一个在线销售应用程序,它不断向数据库发布销售订单。编译运输指令的服务对一批订单进行操作而不是单独处理每个订单。你可以使用Timer来每30分钟启动批处理。
⚠注意:
System.Timers.Timer类具有与系统时钟相同的分辨率。
这意味着如果Interval属性小于系统时钟分辨率,则Elapsed事件将按照系统时钟分辨率定义的时间间隔触发。
Timer组件捕获并抑制事件处理程序为Elapsed事件抛出的所有异常(也就是说,在Elapsed事件处理器中抛出的异常,你在其它线程中无法直接感受到。这就可能导致,如果你在Elapsed事件处理器中没有添加异常处理机制,并且里面抛出异常了,从线程外看好像啥也没发生)。但是要注意,对于异步执行并包含await操作符(在C#中)或await操作符(在VB中)的事件处理程序则不然。这些事件处理程序中抛出的异常会传回调用线程,如下所示:
using System;
using System.Threading.Tasks;
using System.Timers;
class Example
{
static void Main()
{
Timer timer = new Timer(1000);
timer.Elapsed += async ( sender, e ) => await HandleTimer();
timer.Start();
Console.Write("Press any key to exit... ");
Console.ReadKey();
}
private static Task HandleTimer()
{
Console.WriteLine("\nHandler not implemented..." );
throw new NotImplementedException();
}
}
// The example displays output like the following:
// Press any key to exit...
// Handler not implemented...
//
// Unhandled Exception: System.NotImplementedException: The method or operation is not implemented.
// at Example.HandleTimer()
// at Example.<<Main>b__0>d__2.MoveNext()
// --- End of stack trace from previous location where exception was thrown ---
// at System.Runtime.CompilerServices.AsyncMethodBuilderCore.<>c__DisplayClass2.<ThrowAsync>b__5(Object state)
// at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
// at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
// at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
// at System.Threading.ThreadPoolWorkQueue.Dispatch()
若SynchronizingObject属性为null,则Elapsed事件在ThreadPool线程中引发。若Elapsed事件的处理耗时长于Interval,则该事件可能会在另一个ThreadPool线程上再次引发。该情况下,事件处理程序应该是可重入的(reentrant)。
⚠注意:
事件处理方法可能运行在一个线程,同时另一个线程调用Stop方法或将Enabled属性设置为false。这可能导致在定时器停止后引发Elapsed事件。Stop方法的示例代码展示了一种避免这种竞争条件的方法。
后面这部分等学了await再看。
总的来说,System.Threading.Timer和System.Timers.Timer表面上主要异同是,
1️⃣前者是直接调用委托,而后者是引发事件。
2️⃣两者都是运行在系统线程池线程上的
3️⃣两者时钟分辨率都等于系统时钟分辨率
4️⃣前者可能较轻量级,后者是服务器级别的(但这点很模糊,我的理解是一般的桌面程序用前者即可,如果程序相对较大,可能用后者好)
5️⃣System.Timers.Timer会捕获并抑制Elapsed事件处理器抛出的所有异常。
如果你想将这两者用于WPF应用,尤其是MVVM的VM中来实现多线程改变绑定的数据,那可能会达不到预期效果,因为在WPF框架的设定下,非UI线程直接或间接访问UI线程是不合法的。如ObservableCollection之类的集合跨线程访问时,大多会报错System.NotSupportedException。
最后根据官方文档描述,一般都是推荐用后者的。
如何在ruby中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL
我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
我正在寻找一个用ruby演示计时器的在线示例,并发现了下面的代码。它按预期工作,但这个简单的程序使用30Mo内存(如Windows任务管理器中所示)和太多CPU有意义吗?非常感谢deftime_blockstart_time=Time.nowThread.new{yield}Time.now-start_timeenddefrepeat_every(seconds)whiletruedotime_spent=time_block{yield}#Tohandle-vesleepinteravalsleep(seconds-time_spent)iftime_spent
我如何做Ruby方法"Flatten"RubyMethod在C#中。此方法将锯齿状数组展平为一维数组。例如:s=[1,2,3]#=>[1,2,3]t=[4,5,6,[7,8]]#=>[4,5,6,[7,8]]a=[s,t,9,10]#=>[[1,2,3],[4,5,6,[7,8]],9,10]a.flatten#=>[1,2,3,4,5,6,7,8,9,10 最佳答案 递归解决方案:IEnumerableFlatten(IEnumerablearray){foreach(variteminarray){if(itemisIEnume
我最近从C#转向了Ruby,我发现自己无法制作可折叠的标记代码区域。我只是想到做这种事情应该没问题:classExamplebegin#agroupofmethodsdefmethod1..enddefmethod2..endenddefmethod3..endend...但是这样做真的可以吗?method1和method2最终与method3是同一种东西吗?还是有一些我还没有见过的用于执行此操作的Ruby惯用语? 最佳答案 正如其他人所说,这不会改变方法定义。但是,如果要标记方法组,为什么不使用Ruby语义来标记它们呢?您可以使用
什么是Linq聚合方法的ruby等价物。它的工作原理是这样的varfactorial=new[]{1,2,3,4,5}.Aggregate((acc,i)=>acc*i);每次将数组序列中的值传递给lambda时,变量acc都会累积。 最佳答案 这在数学以及几乎所有编程语言中通常称为折叠。它是更普遍的变形概念的一个实例。Ruby从Smalltalk中继承了这个特性的名称,它被称为inject:into:(像aCollectioninject:aStartValueinto:aBlock一样使用。)所以,在Ruby中,它称为inj
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭8年前。Improvethisquestion几年前我去学校学习编程,毕业后我找到了一份系统管理方面的工作,这就是我职业生涯的方向。我想重新开始某种开发,并且一直在“玩”C#和ASP.NET,但我已经听到很多关于其他"new"语言的讨论(新的意思是它们是新的)我)喜欢Ruby和F#。我想我想知道我是否在浪费时间学习主要的MS语言,而不是成为一名通才。很长一段时间没有离开开发社区(如果我曾经离开过的话)让我在潮流中挣扎,我不想落在时代的
我有一个简单的Ruby脚本,我用它在某些HTTPheader上执行private_encrypt以签署要发送到rubyRESTAPI的Web请求,该API会根据Base64编码字符串测试Base64编码字符串生成而不是解码Base64和解密数据然后测试原始字符串。我使用的脚本是require"openssl"require"base64"path_to_cert=ARGV[0].dupplain_text=Base64.decode64(ARGV[1].dup)private_key=OpenSSL::PKey::RSA.new(File.read(path_to_cert))pu
我是ruby开发的新手,我目前正在使用rails2.3.11在ruby1.8.7中开发一个项目,我想知道这种语言是否有与C#的linq等效的集合操作,例如where子句。谢谢。 最佳答案 Ruby中Linq的where等价于find_all检查documentationfortheEnumerableModule用于其他功能。 关于C#的LINQ用于在ruby中等效的集合操作,我们在StackOverflow上找到一个类似的问题: https://