请考虑.net 2.0的以下场景:
我有一个在system.Timers.Timer对象上触发的事件。然后,订户在收到事件后将项目添加到Windows.Forms.Listbox。这会导致跨线程异常。
我的问题是处理这种情况的最佳方法是什么。我提出的解决方案如下:
private delegate void messageDel(string text);
private void ThreadSafeMsg(string text)
{
if (this.InvokeRequired)
{
messageDel d = new messageDel(ThreadSafeMsg);
this.Invoke(d, new object[] { text });
}
else
{
listBox1.Items.Add(text);
listBox1.Update();
}
}
// event
void Instance_Message(string text)
{
ThreadSafeMsg(text);
}
这是在.net 2中处理此问题的最佳方法吗?怎么样.net 3.5?
答案 0 :(得分:1)
您有一个跨线程异常,因为您尝试从UI线程外部访问项目。代表是必要的,以便挂钩消息泵并进行UI更改。
如果您使用表格计时器,那么您将进入UI线程。但是,如果您使用BackgroundWorkerThread并且还需要一个委托,那么您将遇到同样的问题。
答案 1 :(得分:1)
使用Control.InvokeRequired是没有意义的,你知道它始终是。 Elapsed事件是在线程池线程上引发的,从不 UI线程。
这使得使用System.Timers.Timer变得毫无意义,只需使用System.Windows.Forms.Timer即可。无需使用Control.Begin / Invoke,当用户关闭表单时引发事件时,不能使用ObjectDisposedException使程序崩溃。
答案 2 :(得分:1)
在.net 3.5中几乎相同,因为当您从另一个工作线程访问UI线程时,它与Windows窗体和跨线程相关。 但是,您可以通过使用通用Action<>来缩小代码。和Func<&gt ;,避免手动创建代理。
这样的事情:
private void ThreadSafeMsg(string text)
{
if (this.InvokeRequired)
this.Invoke(new Action<string>(ThreadSafeMsg), new object[] { text });
else
{
// Stuff...
}
}
答案 3 :(得分:0)
您的案例中最简单的解决方案 - 使用System.Windows.Forms.Timer类,但在一般情况下,您可以使用以下解决方案从非GUI线程访问您的GUI内容(此解决方案适用于.net 2.0但更多优雅的.net 3.5):
public static class ControlExtentions
{
public static void InvokeIfNeeded(this Control control, Action doit)
{
if (control.InvokeRequired)
control.Invoke(doit);
else
doit();
}
}
你可以像这样使用它来自什么线程,来自UI或来自另一个:
this.InvokeIfNeeded(()=>
{
listBox1.Items.Add(text);
listBox1.Update();
});
答案 4 :(得分:0)
根据您的操作正在做什么,Control.BeginInvoke可能比Control.Invoke更好。 Control.Invoke将等待UI线程在返回之前处理您的消息。如果UI线程被阻止,它将永远等待。 Control.BeginInvoke将为UI线程排队消息并立即返回。因为如果在尝试BeginInvoke它之前立即处理控件,就无法避免异常,您需要捕获(可能吞下)异常(我认为它可能是ObjectDisposedException或IllegalOperationException,具体取决于时间)。当您即将发布消息并在消息处理程序中清除或减少消息时,您还需要设置一个标志或计数器(可能使用Threading.Interlocked.Increment / Decrement),以确保您不会排入过多的数字UI线程被阻止时的消息。