当您从表单内订阅对象上的事件时,实际上是在将对回调方法的控制移交给事件源。您不知道该事件源是否会选择在其他线程上触发事件。
问题在于,调用回调时,您不能假定可以在表单上创建更新控件,因为有时如果在与运行表单的线程不同的线程上调用事件回调,则这些控件将引发异常开启。
要稍微简化Simon的代码,可以使用内置的通用Action委托。它节省了您不需要的大量委托类型,从而使您的代码更加实用。此外,在.NET 3.5中,他们向Invoke方法添加了params参数,因此您不必定义临时数组。
1 2 3 4 5 6 7 8 9 10
| void SomethingHappened(object sender, EventArgs ea)
{
if (InvokeRequired)
{
Invoke(new Action<object, EventArgs>(SomethingHappened), sender, ea);
return;
}
textBox1.Text ="Something happened";
} |
以下是要点:
您不能从不同于在其上创建线程(窗体线程)的线程进行UI控件调用。
委托调用(即事件挂钩)在与触发事件的对象相同的线程上触发。
因此,如果您有一个单独的"引擎"线程来做一些工作,并且有一些UI监视状态变化,这些变化可以反映在UI中(例如进度条或其他),那么您就遇到了问题。引擎触发的是一个对象更改事件,该事件已被Form挂钩。但是在引擎的线程上调用了向引擎注册的Form的回调委托。不在窗体的线程上。因此,您无法从该回调中更新任何控件。 h!
BeginInvoke进行了救援。只要在所有回调方法中使用此简单的编码模型,就可以确保一切都会好起来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| private delegate void EventArgsDelegate(object sender, EventArgs ea);
void SomethingHappened(object sender, EventArgs ea)
{
//
// Make sure this callback is on the correct thread
//
if (this.InvokeRequired)
{
this.Invoke(new EventArgsDelegate(SomethingHappened), new object[] { sender, ea });
return;
}
//
// Do something with the event such as update a control
//
textBox1.Text ="Something happened";
} |
真的很简单。
使用InvokeRequired查找此回调是否在正确的线程上发生。
如果不是,则使用相同的参数在正确的线程上重新调用该回调。您可以使用Invoke(阻止)或BeginInvoke(非阻止)方法来重新调用方法。
下次调用该函数时,InvokeRequired返回false,因为我们现在位于正确的线程上,每个人都很高兴。
这是一种非常紧凑的方法,可以解决此问题并使表单免受多线程事件回调的影响。
在这种情况下,我经常使用匿名方法:
1 2 3 4 5
| void SomethingHappened(object sender, EventArgs ea)
{
MethodInvoker del = delegate{ textBox1.Text ="Something happened"; };
InvokeRequired ? Invoke( del ) : del();
} |
作为lazy programmer,我有一个非常懒惰的方法。
我要做的就是这个。
1 2 3 4 5 6 7 8 9 10 11
| private void DoInvoke(MethodInvoker del) {
if (InvokeRequired) {
Invoke(del);
} else {
del();
}
}
//example of how to call it
private void tUpdateLabel(ToolStripStatusLabel lbl, String val) {
DoInvoke(delegate { lbl.Text = val; });
} |
您可以在函数中内联DoInvoke或将其隐藏在单独的函数中,以为您做脏工作。
请记住,您可以将函数直接传递到DoInvoke方法中。
1 2 3 4 5 6
| private void directPass() {
DoInvoke(this.directInvoke);
}
private void directInvoke() {
textLabel.Text ="Directly passed.";
} |
我对这个话题有些迟了,但是您可能想看看基于事件的异步模式。正确实施后,可以确保始终从UI线程引发事件。
这是一个简短的示例,仅允许一个并发调用;支持多个调用/事件需要更多的工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
| using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public class MainForm : Form
{
private TypeWithAsync _type;
[STAThread()]
public static void Main()
{
Application.EnableVisualStyles();
Application.Run(new MainForm());
}
public MainForm()
{
_type = new TypeWithAsync();
_type.DoSomethingCompleted += DoSomethingCompleted;
var panel = new FlowLayoutPanel() { Dock = DockStyle.Fill };
var btn = new Button() { Text ="Synchronous" };
btn.Click += SyncClick;
panel.Controls.Add(btn);
btn = new Button { Text ="Asynchronous" };
btn.Click += AsyncClick;
panel.Controls.Add(btn);
Controls.Add(panel);
}
private void SyncClick(object sender, EventArgs e)
{
int value = _type.DoSomething();
MessageBox.Show(string.Format("DoSomething() returned {0}.", value));
}
private void AsyncClick(object sender, EventArgs e)
{
_type.DoSomethingAsync();
}
private void DoSomethingCompleted(object sender, DoSomethingCompletedEventArgs e)
{
MessageBox.Show(string.Format("DoSomethingAsync() returned {0}.", e.Value));
}
}
class TypeWithAsync
{
private AsyncOperation _operation;
// synchronous version of method
public int DoSomething()
{
Thread.Sleep(5000);
return 27;
}
// async version of method
public void DoSomethingAsync()
{
if (_operation != null)
{
throw new InvalidOperationException("An async operation is already running.");
}
_operation = AsyncOperationManager.CreateOperation(null);
ThreadPool.QueueUserWorkItem(DoSomethingAsyncCore);
}
// wrapper used by async method to call sync version of method, matches WaitCallback so it
// can be queued by the thread pool
private void DoSomethingAsyncCore(object state)
{
int returnValue = DoSomething();
var e = new DoSomethingCompletedEventArgs(returnValue);
_operation.PostOperationCompleted(RaiseDoSomethingCompleted, e);
}
// wrapper used so async method can raise the event; matches SendOrPostCallback
private void RaiseDoSomethingCompleted(object args)
{
OnDoSomethingCompleted((DoSomethingCompletedEventArgs)args);
}
private void OnDoSomethingCompleted(DoSomethingCompletedEventArgs e)
{
var handler = DoSomethingCompleted;
if (handler != null) { handler(this, e); }
}
public EventHandler<DoSomethingCompletedEventArgs> DoSomethingCompleted;
}
public class DoSomethingCompletedEventArgs : EventArgs
{
private int _value;
public DoSomethingCompletedEventArgs(int value)
: base()
{
_value = value;
}
public int Value
{
get { return _value; }
}
}
} |
在许多简单的情况下,您可以使用MethodInvoker委托,而无需创建自己的委托类型。