invoke和begininvoke区别
- control.invoke(参数delegate)方法:在拥有此控件的基础窗口句柄的线程上执行指定的委托
- control.begininvoke(参数delegate)方法:在创建控件的基础句柄所在线程上异步执行指定委托。
如果你的后台线程在更新一个UI控件的状态后不需要等待,而是要继续往下处理,那么你就应该使用BeginInvoke来进行异步处理。
如果你的后台线程需要操作UI控件,并且需要等到该操作执行完毕才能继续执行,那么你就应该使用Invoke。
invoke例子:
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+"AAA");
invokeThread = new Thread(new ThreadStart(StartMethod));
invokeThread.Start();
string a = string.Empty;
for (int i = 0; i < 3; i++) //调整循环次数,看的会更清楚
{
Thread.Sleep(1000);
a = a + "B";
}
MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+a);
}
private void StartMethod()
{
MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+"CCC");
button1.Invoke(new invokeDelegate(invokeMethod));
MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+"DDD");
}
private void invokeMethod()
{
//Thread.Sleep(3000);
MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "EEE");
}
结论:我们运行后,看下程序的运行顺序,1AAA->3CCC和1BBB->1EEE ->3DDD 。
解释:主线程运行1AAA,然后1BBB和子线程3CCC同时执行,然后通过invoke来将invokemethod方法提交给主线程,然后子线 程等待主线程执行,直到主线程将invokemethod方法执行完成(期间必须等待主线程的任务执行完成,才会去执行invoke提交的任务),最后执 行子线程3DDD。
begininvoke例子:
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+"AAA");
invokeThread = new Thread(new ThreadStart(StartMethod));
invokeThread.Start();
string a = string.Empty;
for (int i = 0; i < 3; i++) //调整循环次数,看的会更清楚
{
Thread.Sleep(1000);
a = a + "B";
}
MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+a);
}
private void StartMethod()
{
MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+"CCC");
button1.BeginInvoke(new invokeDelegate(invokeMethod));
MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+"DDD");
}
private void beginInvokeMethod()
{
//Thread.Sleep(3000);
MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "EEEEEEEEEEEE");
}
结论: 我们运行后看看执行的结果:1AAA->1BBB和3CCC->1EEE和3DDD。
解释: 主线程运行1AAA,然后1BBB和子线程3CCC同时执行,然后通过begininvoke来将invokemethod方法提交给主线程,然后主线程执行1EEE(主线程自己的任务执行完成), 同时子线程继续执行3DDD。
解决从不是创建控件的线程访问它
出现上述错误的原因:.net禁止跨线程调用控件。只有创建界面的主线程才能访问界面上的控件,有两种办法解决该问题:
- 忽略对跨线程调用的检测
在窗体构造函数中设置Control.CheckForIllegalCrossThreadCalls =false。
一般较少使用,容易造成死锁。
public Form1()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = false;
}
- 使用委托
普通委托方法(常用):
delegate void SafeSetText(string strMsg);
private void SetText(string strMsg)
{
if(textbox1.InvokeRequired)
{
textbox1.Invoke(new Action<string>((str)=>
{
SetText(str);
}),strMst;
}
else
{
textbox1.Text=strMsg;
}
}
当从不是创建textbox1的线程访问时,invokerequired=true,通过invoke将settext函数转交给创建textbox1的主线程处理,可以保证窗体中控件的线程安全。
匿名委托:
delegate void SafeSetText(string strMsg);
private void SetText2(string strMsg)
{
SafeSetText objSet = delegate(string str)
{
textBox1.Text = str;
}
textBox1.Invoke(objSet,new object[]{strMsg});
}
其他知识补充
ThreadPool.RegisterWaitForSingleObject
public static RegisteredWaitHandle RegisterWaitForSingleObject(
WaitHandle waitObject,
WaitOrTimerCallback callBack,
Object state,
int millisecondsTimeOutInterval,
bool executeOnlyOnce
)
ThreadPool.RegisterWaitForSingleObject中必须传入一个WaitHandle,这个WaitHandle一旦接受到信号,或者没接收信号而超时了,则会调用WaitOrTimerCallback方法。这个超时时间由millisecondsTimeOutInterval
设置的。如果超时时间设为-1,那么只要接受不到信号,那么WaitOrTimerCallback则一直不会运行。该方法不会阻塞线程,只是在线程池上开启一个线程来处理回调。
executeOnlyOnce
的值为true/false。如果为 true,表示在调用了委托后,线程将不再在 waitObject 参数上等待;如果为 false,表示每次完成等待操作后都重置计时器,直到注销等待。state为传递给callback的对象。
state
为传递给回调函数的对象
waitobject
可为Mutex,ManualReseanutEvent,AutoResetEvent,ManualReseanutEvent调用set后自动将开关量值将一直保持为true,AutoResetEvent调用Set方法将变为true随后立即变为false,可以将它理解为一个脉冲。
此方法返回的 RegisteredWaitHandle 使用完毕后,请调用其 RegisteredWaitHandle.Unregister 方法来释放对等待句柄的引用。 我们建议始终调用 RegisteredWaitHandle.Unregister 方法,即使将 executeOnlyOnce 指定为 true 也是如此
例子1:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace test
{
class Class14
{
static AutoResetEvent wait=new AutoResetEvent(false);
static void Main(string[] args)
{
object state=new object();
ThreadPool.RegisterWaitForSingleObject(wait, new WaitOrTimerCallback(test1), state,5000, false);
Console.ReadKey();
}
private static void test1(object state, bool timedOut)
{
Console.WriteLine("aaa");
}
}
}
运行结果:每5s输出一次test1
例子2:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace test
{
class Class14
{
static ManualResetEvent wait2=new ManualResetEvent(false);
static AutoResetEvent wait=new AutoResetEvent(false);
static void Main(string[] args)
{
object state=new object();
ThreadPool.RegisterWaitForSingleObject(wait, new WaitOrTimerCallback(test11), state,5000, false);
wait.Set();
Console.ReadKey();
}
private static void test11(object state, bool timedOut)
{
Console.WriteLine("aaa");
}
}
}
运行结果:set函数运行时立即输出aaa,然后每5s输出一次aaa
例子3:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace test
{
class Class14
{
static ManualResetEvent wait=new ManualResetEvent(true);
static void Main(string[] args)
{
object state=new object();
ThreadPool.RegisterWaitForSingleObject(wait, new WaitOrTimerCallback(test11), state,5000, false);
Console.ReadKey();
}
private static void test11(object state, bool timedOut)
{
Console.WriteLine("aaa");
}
}
}
运行结果:因为manualresetevent设置为true,所以会一直触发,一直输出aaa
SynchoronizationContext
SynchoronizationContext实现的功能就是一个线程和另外一个线程的通讯,和beginInvoke()
和Invoke()
类似。
send()
是简单的在当前线程上去调用委托来实现(同步调用)。也就是在子线程上直接调用UI线程执行,等UI线程执行完成后子线程才继续执行。
Post()
是在线程池上去调用委托来实现(异步调用)。这是子线程会从线程池中找一个线程去调UI线程,子线程不等待UI线程的完成而直接执行自己下面的代码。