SynchronizingObject与Invoke

时间:2011-01-31 11:55:55

标签: c# multithreading winforms user-interface timer

在我的表格中,我添加了一种方法来“淡化”它。这样可以使用System.Timers.TimerElapsed事件使用委托来更改表单的不透明度。这是代码:

public void FadeOut()
{
    // Timer for transition
    Timer fade = new Timer();

    // Transition code
    fade.Elapsed += delegate
    {
        this.Opacity += 0.05;

        if (this.Opacity >= .95)
        {
            this.Opacity = 1;
            fade.Enabled = false;
            fade.Dispose();
        }
    };

    fade.Interval = 100;
    fade.Enabled = true;
}

这导致“跨线程操作无效”错误,这是我看到的常见障碍。所以我四处寻找解决方案,第一个涉及使用.BeginInvoke和代码块来控制与控件相同的线程。但我发现这看起来很笨重,所以我一直在寻找然后发现SynchronizingObject的{​​{1}}属性。这似乎更好,因为它只需要一行额外的代码:

System.Timers.Timer

现在代码运行正常。但我真的很困惑,为什么所有需要的是将// Timer for transition Timer fade = new Timer(); fade.SynchronizingObject = this; 设置为表单控件时,为什么有很多解决方案建议使用BeginInvoke/Invoke

5 个答案:

答案 0 :(得分:3)

主要是因为使用该属性毫无意义。是的,它确保Elapsed事件处理程序在UI线程上运行。但现在它只是与System.Windows.Forms.Timer做同样的事情。

虽然不太好,但情况更糟。因为它不能保证在禁用Elapsed后不会调用它。禁用它不会刷新任何挂起的调用,也不会刷新尚未准备好运行的TP线程。如果Interval与处理程序完成的工作量相比较小,则可能有数百个。

你绝对想要一个System.Windows.Forms.Timer。你没有在线程池线程上做任何有用的工作。

答案 1 :(得分:2)

我不确定,但我相信Timer会在Invoke属性内部使用BeginInvokeSynchronizingObject

让我们说这个属性只是给开发人员一些抽象;让他的生活更轻松。

我的猜测确实是正确的,这就是Reflector告诉我们MyTimerCallback的{​​{1}}私有成员方法:

System.Timers.Timer

答案 2 :(得分:1)

为什么不使用WinForms timer?这基于窗口消息,并始终在UI线程中运行;既然你想要在UI线程需要抽取消息的情况下执行更新,这可能是一个更好的解决方案(不需要同步/阻塞)。

答案 3 :(得分:0)

像这样的计时器解决方案确实有点像黑客。

你最好编写一个正确的异步线程并使用 BeginInvoke或SynchronizationContext

正如你正确地观察到的那样,它远不是单行,而是多任务,正确完成,永远不会。

答案 4 :(得分:0)

重点是代码的范围。整个事件处理程序?或只是UI更改代码。

timer.SynchronizingObject使得在给定对象的线程上调用事件处理程序。如果在“Form1”类中将其设置为“this”,则表示即使您创建了基于线程的计时器,所有用于处理计时器事件的代码也都由“Form1”实例的同一个线程运行。所以这没什么。同样作为windows形成计时器。

您想要这样做的原因是因为您想要访问UI控件并在Form1中进行一些更改,但如果您使用Windows窗体计时器,整个UI都会挂起。因为它们都在同一个线程上执行。

要避免这种情况,请使用基于线程的计时器。您的事件处理程序在来自某个系统线程池的另一个线程上调用。这解决了挂起UI的问题。然而,这带来了另一个问题。跨线程访问UI控制异常。在某些版本的Visual Studio上,您可以在调试版本上禁用此检查。但是你无法绕过对发布版本的检查。此线程检查只是为了防止多线程导致的崩溃。那么你需要使用所有这些调用和委托的东西。

这里的重要部分是,它是重定向的唯一几行代码,可以在Form1线程上运行。不是整个事件处理程序。大多数事件处理代码都在另一个线程上运行。这包括在网络或磁盘上做某事的代码。

这就是差异。

但是,所有这些仅适用于Windows窗体应用程序。对于WPF,只需使用调度程序计时器。 (这就是为什么你在WinForm中找不到Windows.Threading的原因,因为你不能在WinForm中使用调度程序计时器,但在WPF中可用)