从不是创建控件的线程访问它和相关知识

invoke和begininvoke区别

  1. control.invoke(参数delegate)方法:在拥有此控件的基础窗口句柄的线程上执行指定的委托
  2. 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禁止跨线程调用控件。只有创建界面的主线程才能访问界面上的控件,有两种办法解决该问题:

  1. 忽略对跨线程调用的检测

在窗体构造函数中设置Control.CheckForIllegalCrossThreadCalls =false。
一般较少使用,容易造成死锁

public Form1()
{
    InitializeComponent();
    CheckForIllegalCrossThreadCalls = false;
}
  1. 使用委托

普通委托方法(常用):

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线程的完成而直接执行自己下面的代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值