在我的表格中,我添加了一种方法来“淡化”它。这样可以使用System.Timers.Timer
,Elapsed
事件使用委托来更改表单的不透明度。这是代码:
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
?
答案 0 :(得分:3)
主要是因为使用该属性毫无意义。是的,它确保Elapsed事件处理程序在UI线程上运行。但现在它只是与System.Windows.Forms.Timer做同样的事情。
虽然不太好,但情况更糟。因为它不能保证在禁用Elapsed后不会调用它。禁用它不会刷新任何挂起的调用,也不会刷新尚未准备好运行的TP线程。如果Interval与处理程序完成的工作量相比较小,则可能有数百个。
你绝对想要一个System.Windows.Forms.Timer。你没有在线程池线程上做任何有用的工作。
答案 1 :(得分:2)
我不确定,但我相信Timer会在Invoke
属性内部使用BeginInvoke
或SynchronizingObject
。
让我们说这个属性只是给开发人员一些抽象;让他的生活更轻松。
我的猜测确实是正确的,这就是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中可用)