在C#中无限/定期执行代码的最佳实践

Tok*_*okk 23 .net c# events winapi execution

通常在我的代码中,我开始的威胁基本上是这样的:

void WatchForSomething()
{
    while(true)
    {
        if(SomeCondition)
        {
             //Raise Event to handle Condition
             OnSomeCondition();
        }
        Sleep(100);
    }
}
Run Code Online (Sandbox Code Playgroud)

只是为了知道某些条件是否为真(例如,如果一个错误的编码库没有事件,只需要布尔变量,我需要它们的"实时视图").

现在我想知道是否有更好的方法来完成这种工作,就像一个Windows函数挂钩,可以运行我的方法所有x秒.或者我应该为我的应用程序编写一个全局事件,提高所有x秒并让他调用我的方法如下:

//Event from Windows or selfmade
TicEvent += new TicEventHandler(WatchForSomething));
Run Code Online (Sandbox Code Playgroud)

然后这个方法:

    void WatchForSomething()
    {
        if(SomeCondition)
        {
             //Raise Event to handle Condition
             OnSomeCondition();
        }
    }
Run Code Online (Sandbox Code Playgroud)

因此,我希望这不会因为一个"主观问题"或其他问题而被关闭,我只想知道这类工作的最佳实践是什么.

LBu*_*kin 18

编写长时间运行的事件处理代码不一定是"最好的方法".这取决于您正在开发的应用程序类型.

您展示的第一个示例是您经常会看到编写长时间运行线程的main方法的惯用方法.虽然通常需要使用互斥或​​等待事件同步原语而不是调用Sleep()- 否则它是用于实现事件处理循环的典型模式.这种方法的好处是它允许专门的处理在一个单独的线程上运行 - 允许应用程序的主线程执行其他任务或保持对用户输入的响应.这种方法的缺点是它可能需要使用内存屏障(例如锁)来确保共享资源不会被破坏.这也使得更新UI变得更加困难,因为您通常必须将此类调用封送回UI线程.

第二种方法通常也被使用 - 特别是在已经具有事件驱动API的系统中,例如WinForms,WPF或Silverlight.如果没有用户启动的事件触发您的处理,则使用计时器对象或空闲事件是可以进行定期后台检查的典型方式.这样做的好处是可以轻松地交互和更新用户界面对象(因为它们可以直接从同一个线程访问),并且可以减少锁和互斥锁对受保护数据的需求.这种方法的一个潜在缺点是,如果必须执行的处理非常耗时,则可能会使您的应用程序对用户输入无响应.

如果您不编写具有用户界面的应用程序(例如服务),则会更频繁地使用第一个表单.

暂时......如果可能的话,最好使用像EventWaitHandleSemaphore这样的同步对象来表示何时可以处理工作.这允许您避免使用Thread.Sleep和/或Timer对象.它减少了可以执行工作和触发事件处理代码之间的平均延迟,并且它最大限度地减少了使用后台线程的开销,因为运行时环境可以更有效地调度它们,并且不会消耗任何CPU周期直到有工作要做.

还值得一提的是,如果您所做的处理是响应与外部源(MessageQueues,HTTP,TCP等)的通信,您可以使用WCF等技术来提供事件处理代码的框架.WCF提供了基类,使得实现异步响应通信事件活动的客户端和服务器系统变得更加容易.


Mar*_*k H 12

如果您看一下Reactive Extensions,它提供了一种使用可观察模式执行此操作的优雅方法.

var timer = Observable.Interval(Timespan.FromMilliseconds(100));
timer.Subscribe(tick => OnSomeCondition());
Run Code Online (Sandbox Code Playgroud)

关于observables的一个好处是能够组合和组合现有的observable,甚至使用LINQ表达式来创建新的observable.例如,如果你想让第二个计时器与第一个计时器同步,但每1秒只触发一次,你可以说

var seconds = from tick in timer where tick % 10 == 0 select tick;
seconds.Subscribe(tick => OnSomeOtherCondition());
Run Code Online (Sandbox Code Playgroud)


Ran*_*Ran 10

顺便说一句,Thread.Sleep可能永远不是一个好主意.

Thread.Sleep人们通常不了解的一个基本问题是,内部实现Thread.Sleep 不会抽取STA消息.最好,最简单的选择,如果你需要等待一定时间,不能使用内核同步对象,是替代Thread.SleepThread.Join当前线程上,与想要的超时.Thread.Join将表现相同,即线程将等待所需的时间,但同时将抽取STA对象.

为什么这很重要(一些详细的解释如下)?

有时,在您不知道的情况下,您的一个线程可能已经创建了一个STA COM对象.(例如,当您使用Shell API时,这有时会发生在幕后).现在假设你的一个线程已经创建了一个STA COM对象,现在正在调用它Thread.Sleep.如果在某个时候必须删除COM对象(可能在GC意外的时间发生),则Finalizer线程将尝试调用对象的distruvtor.此调用将被编组到对象的STA线程,该线程将被阻止.

现在,事实上,你将有一个被阻止的终结者线程.在这种情况下,对象不能从内存中释放出来,并且会出现不好的事情.

所以底线:Thread.Sleep=坏.Thread.Join=合理的选择.


Jim*_*hel 6

您展示的第一个示例是实现周期性计时器的一种相当不优雅的方式..NET有许多计时器对象,使得这种事情几乎是微不足道的.调查System.Windows.Forms.Timer,System.Timers.TimerSystem.Threading.Timer.

例如,以下是您使用a System.Threading.Timer替换第一个示例的方法:

System.Threading.Timer MyTimer = new System.Threading.Timer(CheckCondition, null, 100, 100);

void CheckCondition(object state)
{
    if (SomeCondition())
    {
        OnSomeCondition();
    }
}
Run Code Online (Sandbox Code Playgroud)

该代码将CheckCondition每100毫秒(或左右)调用一次.