1

我想这可能会被标记为重复和封闭,但我一生都找不到这个问题的清晰、简洁的答案。所有的回复和资源几乎都专门处理 Windows 窗体并利用预构建的实用程序类,例如 BackgroundWorker。我非常想了解这个概念的核心,这样我就可以将基础知识应用到其他线程实现中。

我想要实现的一个简单示例:

//timer running on a seperate thread and raising events at set intervals
//incomplete, but functional, except for the cross-thread event raising
class Timer
{
    //how often the Alarm event is raised
    float _alarmInterval;
    //stopwatch to keep time
    Stopwatch _stopwatch;
    //this Thread used to repeatedly check for events to raise
    Thread _timerThread;
    //used to pause the timer
    bool _paused;
    //used to determine Alarm event raises
    float _timeOfLastAlarm = 0;

    //this is the event I want to raise on the Main Thread
    public event EventHandler Alarm;

    //Constructor
    public Timer(float alarmInterval)
    {
        _alarmInterval = alarmInterval;
        _stopwatch = new Stopwatch();
        _timerThread = new Thread(new ThreadStart(Initiate));
    }

    //toggles the Timer
    //do I need to marshall this data back and forth as well? or is the
    //_paused boolean in a shared data pool that both threads can access?
    public void Pause()
    {
        _paused = (!_paused);            
    }

    //little Helper to start the Stopwatch and loop over the Main method
    void Initiate()
    {
        _stopwatch.Start();
        while (true) Main();    
    }

    //checks for Alarm events
    void Main()
    {
        if (_paused && _stopwatch.IsRunning) _stopwatch.Stop();
        if (!_paused && !_stopwatch.IsRunning) _stopwatch.Start();
        if (_stopwatch.Elapsed.TotalSeconds > _timeOfLastAlarm)
        {
            _timeOfLastAlarm = _stopwatch.Elapsed.Seconds;
            RaiseAlarm();
        }
    }
}

这里有两个问题;主要是,我如何将事件发送到主线程以提醒有关方警报事件。

其次,关于 Pause() 方法,该方法将由运行在主线程上的对象调用;我可以通过调用 _stopwatch.start()/_stopwatch.stop() 直接操作在后台线程上创建的秒表吗?如果没有,主线程是否可以如上所示调整 _paused 布尔值,以便后台线程可以看到 _paused 的新值并使用它?

我发誓,我已经完成了我的研究,但是这些(基本的和关键的)细节还没有让我清楚。

免责声明:我知道有可用的类将提供我在 Timer 类中描述的确切特定功能。(事实上​​,我相信这个类就是这样命名的,Threading.Timer)但是,我的问题不是试图帮助我实现 Timer 类本身,而是理解如何执行驱动它的概念。

4

2 回答 2

1

您不能在现有线程上神奇地执行代码。
相反,您需要现有线程显式执行您的代码,使用线程安全的数据结构来告诉它要做什么。

这就是Control.Invoke工作方式(这反过来又是BackgroundWorker工作方式)。
WiinForms 运行一个消息循环,Application.Run()其中大致如下所示:

while(true) {
    var message = GetMessage();  //Windows API call
    ProcessMessage(message);
}

Control.Invoke()发送一条 Windows 消息(使用 Windows 中的线程安全消息传递代码)告诉它运行您的委托。 ProcessMessage(在 UI 线程上执行)将捕获该消息并执行委托。

如果您想自己执行此操作,则需要编写自己的循环。为此,您可以使用 .Net 4.0 中新的线程安全生产者-消费者集合,或者您可以使用委托字段(带有Interlocked.CompareExchange)和 anAutoResetEvent并自己做。

于 2012-06-27T16:35:03.267 回答
1

注意:我在这里写这个是因为评论没有足够的空间,这当然不是一个完整的答案,也不是一个完整的答案:

我一直使用事件来表示不相关的代码来做某事,所以这就是我描述我的意图的方式。不过请原谅我,我不确定我是否看到了编组和事件与编组另一种类型的数据(信号)之间的区别。

从概念上讲,两者都可以视为事件。使用提供的同步/信号对象和尝试自己实现类似的东西之间的区别在于谁以及如何完成工作。

.net 中的事件只是一个委托,一个指向在事件提供者触发它时应该执行的方法的指针列表。如果我理解正确的话,您所说的(编组事件)是在发生某些事情共享事件对象,而 signalig 的概念通常谈论一个开始共享的对象,并且两个线程都“知道”发生了一些事情通过手动或自动检查其状态(依赖于 .net 和 windows 提供的工具)。

在最基本的场景中,你可以通过一个boolean变量来实现这样一个信令概念,一个线程不断循环检查boolean是真实的,而另一种设置是这样的,作为一种信号发生的方式。.NET 提供的不同信号工具以更少资源浪费的方式做到这一点,只要没有信号(布尔值等于 false),也不执行等待线程,但从概念上讲,这是相同的想法。

于 2012-06-27T17:47:21.017 回答