C#中Delegate/Control的Invoke/BeginInvoke/EndInvoke

目录

一、前言

二、背景

三、Delegate的Invoke/BeginInvoke/EndInvoke

1、基于[需求1]

1.1、直接在主线程中运行“耗时操作”

1.2、通过Thread将“耗时操作”放在子线程中运行

1.3、通过Delegate.BeginInvoke()将“耗时操作”放在子线程中运行

1.4、总结

2、基于[需求2]

2.1、一去不复返

2.2、去了还要回->轮询

2.3、去了还要回->WaitOne

2.4、去了还要回->回调

3、Delegate.Invoke()

四、Control的Invoke/BeginInvoke/EndInvoke

1、基于[需求3]

1.1、禁用“检查是否跨线程访问控件”,这是不安全的做法

1.2、Control.Invoke(Delegate)

1.3、Control.BeginInvoke(Delegate)

1.4、总结

五、参考


一、前言

        此文章是我阅读了许多前人的文章、博客后,根据自己的理解做的总结、记录。有不正确的地方望各位多加指正,有侵权的地方望各位提醒我添加引用备注出处。

        此文章迭代更新中(20221201-1619)。

        对C#中的Delegate、Event、Thread,WinForm中的Control有基本了解。

二、背景

        当运行一个WinForm窗体时,UI线程(主线程)上运行着一个消息循环,可以理解“消息循环”是一个死循环,每次循环都会从“消息队列”中取出一个“消息”(WinForm窗体中的用户操作,比如鼠标单击/双击/移入/移出操作,键盘输入等),然后处理这个“消息”,体现在WinForm窗体上就是实时地响应了用户的操作。

        如果有个[需求1]:要在点击WinForm窗体中的一个Button后,执行一个非常耗时的操作(比如,sleep 10s)。如果直接在主线程中执行这个耗时操作,主线程因为不能进行消息循环导致WinForm窗体看起来卡死,无法响应用户操作。所以,此时应该创建一个子线程,然后将耗时操作放在子线程中执行,此时,会用到Thread,或者Delegate.BeginInvoke()来创建子线程。

        如果有个[需求2]:在[需求1]的基础上,“耗时操作”在子线程中执行完后,需要获取“耗时操作”的执行结果。

        如果有个[需求3]:在[需求1]、[需求2]的基础上,“耗时操作”在子线程中执行完后,需要刷新WinForm中的某个Control(比如,需要刷新TextBox文本框中的值。此时也可以理解为是[需求2]的一种特殊情况)。如果“刷新操作”也放在子线程中执行,会抛出运行时异常(跨线程访问控件,即控件是在主线程中创建的,但是却在子线程中对控件进行了刷新)。所以,此时应该将“刷新操作”整体封装成一个Delegate,然后通过Control.Invoke(Delegate)、Control.BeginInvoke(Delegate)将“刷新操作”交给主线程执行,就能解决“跨线程访问控件”的运行时异常。

三、Delegate对象的Invoke/BeginInvoke/EndInvoke

1、基于[需求1]

1.1、直接在主线程中运行“耗时操作”

public Form1()
{
    InitializeComponent();
    Debug.Listeners.Add(new ConsoleTraceListener());
}

private void button1_Click(object sender, EventArgs e)
{
    Console.WriteLine(string.Format("Begin Click ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    Thread.Sleep(10000);
    Console.WriteLine(string.Format("End Click ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    return;
}

protected override void WndProc(ref Message m)
{
    Debug.WriteLine(m.Msg.ToString());
    base.WndProc(ref m);

    return;
}

        点击Button后,主线程Sleep 10s。此时WinForm看起来卡死,不响应用户在UI中的操作;Console输出的用户在UI中的操作信息看起来也卡住。

        当主线程Sleep结束后,WinForm才响应用户在UI中的操作;Console才继续输出主线程Sleep期间用户在UI中的操作信息。

1.2、通过Thread将“耗时操作”放在子线程中运行

private void button1_Click(object sender, EventArgs e)
{
    Console.WriteLine(string.Format("Begin Click ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    Thread thread = new Thread(new ThreadStart(SubThreadMethod));
    thread.IsBackground = true;
    thread.Start();
    Console.WriteLine(string.Format("End Click ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    return;
}

private void SubThreadMethod()
{
    Console.WriteLine(string.Format("Begin SubThread ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    Thread.Sleep(10000);
    Console.WriteLine(string.Format("End SubThread ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    return;
}

        点击Button后,通过new Thread()创建一个子线程,子线程执行Thread封装的SubThreadMethod,即Sleep 10s;主线程上仍然运行着消息循环。此时WinForm仍然可以响应用户在UI中的操作。

1.3、通过Delegate对象.BeginInvoke()将“耗时操作”放在子线程中运行

private void button1_Click(object sender, EventArgs e)
{
    Console.WriteLine(string.Format("Begin Click ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    CalcDel myCalcDel = new CalcDel(ExeMethod);
    Console.WriteLine(string.Format("Before BeginInvoke ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    myCalcDel.BeginInvoke(null, null);

    Console.WriteLine(string.Format("End Click ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    return;
}

public delegate void CalcDel();

public void ExeMethod()
{
    Console.WriteLine(string.Format("Start ExeMethod ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    Thread.Sleep(10000);
    Console.WriteLine(string.Format("End ExeMethod ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    return;
}

        点击Button后,通过Delegate对象.BeginInvoke()创建一个子线程,子线程执行Delegate对象封装的ExeMethod,即Sleep 10s;主线程上仍然运行着消息循环。效果看起来等同于[1.2]

1.4、总结

        对比2、3发现,Delegate对象.BeginInvoke()实质上是Thread,创建了一个子线程,然后在子线程中运行Delegate对象封装的操作。

        了解了Delegate对象.BeginInvoke()后,再详细对比下Delegate对象.BeginInvoke()、Delegate对象.EndInvoke()的若干用法。

2、基于[需求2]

此段内容,参考文章[WinForm二三事(二)异步操作 - 横刀天笑 - 博客园],并进行完善

2.1、一去不复返

        1.3中展示的就是,子线程运行的“耗时操作”结束后,主线程不需要获取任何关于子线程结束的信息。

2.2、去了还要回->轮询

private void button1_Click(object sender, EventArgs e)
{
    Console.WriteLine(string.Format("Begin Click ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    Func<int> myAction = new Func<int>(ExeMethod);
    Console.WriteLine(string.Format("Begin BeginInvoke ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    IAsyncResult asynResult = myAction.BeginInvoke(null, null);
    Console.WriteLine(string.Format("End BeginInvoke ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    DateTime lastTime = DateTime.MinValue;
    while (!asynResult.IsCompleted)
    {
        DateTime curTime = DateTime.Now;
        if((curTime - lastTime).TotalSeconds > 2){
            Console.WriteLine(string.Format("NotComplete ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
            lastTime = curTime;
        }
    }
    Console.WriteLine(string.Format("Complete ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    int result = myAction.EndInvoke(asynResult);

    Console.WriteLine(string.Format("End Click result:{0} ThreadId:{1} {2}", result, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    return;
}

private int ExeMethod()
{
    Console.WriteLine(string.Format("Begin ExeMethod ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    Thread.Sleep(10 * 1000);
    Console.WriteLine(string.Format("End ExeMethod ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    return 25;
}

        点击Button后,通过Delegate对象.BeginInvoke()创建一个子线程,子线程执行Delegate对象封装的ExeMethod,即Sleep 10s;主线程上运行着循环,监视子线程是否执行完毕。

        如果子线程没有执行完毕,asynResult.IsCompleted始终为false,主线程中的循环一直进行;如果子线程执行完毕,asynResult.IsCompleted变成true,主线程跳出循环。

        主线程在此while循环期间,因为主线程没有运行消息循环,所以WinForm看起来卡死,不响应用户在UI中的操作。

        主线程、子线程分别在哪些时间段执行,是由调度算法分配的。比如,截图中,Delegate对象.BeginInvoke()语句执行后,并没有立即执行由Delegate对象.BeginInvoke()语句创建的子线程,而是由调度算法决定什么时候执行子线程中的语句:

        1、先拥有控制权的主线程执行了一阵子while循环,然后把控制权交给子线程执行ExeMethod;

        2、当子线程Sleep后,又将控制权交给主线程执行while循环;

        3、主线程执行了一阵子while循环,等子线程Sleep结束后,将控制权交给子线程;

        4、子线程执行完毕后,IAsyncResult对象.IsCompleted由false变为true,控制权交给主线程;

        5、主线程跳出while循环。

IAsyncResult asynResult = myAction.BeginInvoke(null, null);

while(!asynResult.IsCompleted){}

        会使执行该语句的当前线程一直循环,直到子线程运行完Delegate对象封装的操作。

2.3、去了还要回->WaitOne

private void button1_Click(object sender, EventArgs e)
{
    Console.WriteLine(string.Format("Begin Click ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
 
    Func<int> myAction = new Func<int>(ExeMethod);
    Console.WriteLine(string.Format("Begin BeginInvoke ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    IAsyncResult asynResult = myAction.BeginInvoke(null, null);
    Console.WriteLine(string.Format("End BeginInvoke ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    if (asynResult.AsyncWaitHandle.WaitOne(5 * 1000, true))
    {
        Console.WriteLine(string.Format("Begin wait ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        int result = myAction.EndInvoke(asynResult);
        Console.WriteLine(string.Format("End wait result:{0} ThreadId:{1} {2}", result, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    }
    Console.WriteLine(string.Format("End Click ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    return;
}
 
private int ExeMethod()
{
    Console.WriteLine(string.Format("Begin ExeMethod ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    Thread.Sleep(10 * 1000);
    Console.WriteLine(string.Format("End ExeMethod ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
 
    return 25;
}

         1、点击Button后,主线程执行到IAsyncResult asynResult = myAction.BeginInvoke(null, null)时,会创建一个子线程跑ExeMethod,不过此时主线程还拥有控制权,所以主线程继续执行到asynResult.AsyncWaitHandle.WaitOne(5 * 1000, true),接着主线程会阻塞等待5s(因为子线程还没执行完毕,所以WaitOne让主线程阻塞等待),此时主线程的控制权被调度算法收回;

        [主线程在此阻塞等待期间,因为主线程没有运行消息循环,所以WinForm看起来卡死,不响应用户在UI中的操作]

        2、调度算法将控制权交给子线程,子线程Sleep 10s,此时子线程的控制权被调度算法收回;

        3、5s后,主线程结束了阻塞等待,调度算法将控制权交给主线程,主线程跳过if(asynResult.AsyncWaitHandle.WaitOne(5 * 1000, true)){}花括号中的语句(因为子线程还没执行完毕,所以if条件不满足),直接执行if(){}之后的语句,然后主线程结束,主线程的控制权被调度算法收回;

        4、又过了5s后,子线程结束了Sleep,调度算法将控制权交给子线程,然后子线程结束,子线程的控制权被调度算法收回。

private void button1_Click(object sender, EventArgs e)
{
    Console.WriteLine(string.Format("Begin Click ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    Func<int> myAction = new Func<int>(ExeMethod);
    Console.WriteLine(string.Format("Begin BeginInvoke ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    IAsyncResult asynResult = myAction.BeginInvoke(null, null);
    Console.WriteLine(string.Format("End BeginInvoke ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    if (asynResult.AsyncWaitHandle.WaitOne(10 * 1000, true))
    {
        Console.WriteLine(string.Format("Begin wait ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        int result = myAction.EndInvoke(asynResult);
        Console.WriteLine(string.Format("End wait result:{0} ThreadId:{1} {2}", result, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    }
    Console.WriteLine(string.Format("End Click ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    return;
}

private int ExeMethod()
{
    Console.WriteLine(string.Format("Begin ExeMethod ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    Thread.Sleep(5 * 1000);
    Console.WriteLine(string.Format("End ExeMethod ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    return 25;
}

        1、点击Button后,主线程执行到IAsyncResult asynResult = myAction.BeginInvoke(null, null)时,会创建一个子线程跑ExeMethod,不过此时主线程还拥有控制权,所以主线程继续执行到asynResult.AsyncWaitHandle.WaitOne(10 * 1000, true),接着主线程会阻塞等待10s(因为子线程还没执行完毕,所以WaitOne让主线程阻塞等待),此时主线程的控制权被调度算法收回;

        [主线程在此阻塞等待期间,因为主线程没有运行消息循环,所以WinForm看起来卡死,不响应用户在UI中的操作]

        2、调度算法将控制权交给子线程,子线程Sleep 5s,此时子线程的控制权被调度算法收回;

        3、5s后,子线程结束了Sleep,调度算法将控制权交给子线程,然后子线程结束,子线程的控制权被调度算法收回;

        4、主线程直接结束了阻塞等待(不用再等5s,因为此时子线程已经结束了,WaitOne是最多等ns,而不是必须等ns),调度算法将控制权交给主线程,主线程执行if(asynResult.AsyncWaitHandle.WaitOne(5 * 1000, true)){}花括号中的语句(因为子线程已经执行完毕,所以if条件满足),接着执行if(){}之后的语句,然后主线程结束,主线程的控制权被调度算法收回。

IAsyncResult asynResult = myAction.BeginInvoke(null, null);

asynResult.AsyncWaitHandle.WaitOne(10 * 1000, true);

会使执行该语句的当前线程等待最多10s。

2.4、去了还要回->回调

private void button1_Click(object sender, EventArgs e)
{
    Console.WriteLine(string.Format("Begin Click ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    Func<int> myAction = new Func<int>(ExeMethod);
    Console.WriteLine(string.Format("Begin BeginInvoke ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    IAsyncResult asynResult = myAction.BeginInvoke(new AsyncCallback((result) =>
    {
        Console.WriteLine(string.Format("Begin wait ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        int res = myAction.EndInvoke(result);
        Console.WriteLine(string.Format("End wait result:{0} ThreadId:{1} {2}", res, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    }), null);

    Console.WriteLine(string.Format("End BeginInvoke ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    Console.WriteLine(string.Format("End Click ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    return;
}

private int ExeMethod()
{
    Console.WriteLine(string.Format("Begin ExeMethod ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    Thread.Sleep(10 * 1000);
    Console.WriteLine(string.Format("End ExeMethod ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    return 25;
}

   

        1、点击Button后,主线程执行到IAsyncResult asynResult = myAction.BeginInvoke(new AsyncCallback((result) => {}), null)时,会创建一个子线程跑ExeMethod,不过此时主线程还拥有控制权,所以主线程继续执行直到主线程结束,此时主线程的控制权被调度算法收回;

        2、调度算法将控制权交给子线程,子线程Sleep 10s,此时子线程的控制权被调度算法收回;

        [子线程在Sleep期间,因为主线程有运行消息循环,所以WinForm看起来没有卡死,会时刻响应用户在UI中的操作]

        3、10s后,子线程结束了Sleep,调度算法将控制权交给子线程,子线程执行完ExeMethod后,接着执行回调函数(result) => {},然后子线程结束,此时子线程的控制权被调度算法收回。

CalcDel myCalDel = null;

private void button1_Click(object sender, EventArgs e)
{
    Console.WriteLine(string.Format("Begin Click ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    myCalDel = new CalcDel(ExecuteTask);
    for (int i = 3; i < 11; i++)
    {
        Console.WriteLine(string.Format("{0}-Begin BeginInvoke ThreadId:{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        myCalDel.BeginInvoke(10 * i, 1000 * i, new AsyncCallback(MyCallBack), i);
        Console.WriteLine(string.Format("{0}-End BeginInvoke ThreadId:{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    }

    Console.WriteLine(string.Format("End Click ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    return;
}


public delegate int CalcDel(int num, int ms);

private int ExeMethod(int num, int ms)
{
    Console.WriteLine(string.Format("Begin ExeMethod ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    Thread.Sleep(ms);
    Console.WriteLine(string.Format("End ExeMethod ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    return num * num;
}

private void MyCallback(IAsyncResult result)
{
    Console.WriteLine(string.Format("Begin {0}-Callback ThreadId:{1} {2}", result.AsyncState.ToString(), Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    int res = myCalDel.EndInvoke(result);
    Console.WriteLine(string.Format("End {0}-Callback 结果:{1} ThreadId:{2} {3}", result.AsyncState.ToString(), res, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    return;
}

        了解了Delegate.BeginInvoke()、Delegate.EndInvoke()的若干用法,再对比下Delegate.Invoke()。

3、Delegate.Invoke()

        Delegate.Invoke()不会创建子线程,而是直接在当前线程中运行Delegate封装的操作。

四、Control的Invoke/BeginInvoke/EndInvoke

1、基于[需求3]

此段内容,参考文章[浅谈Invoke 和 BegionInvoke的用法 - 大艺术家007 - 博客园]

1.1、禁用“检查是否跨线程访问控件”,这是不安全的做法

[待完善...]

1.2、Control.Invoke(Delegate)

private void button1_Click(object sender, EventArgs e)
{
    Console.WriteLine(string.Format("Begin Click ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    Thread subThread = new Thread(new ThreadStart(SubThreadMethod));
    subThread.Start();
    Console.WriteLine(string.Format("End subThread.Start ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    string a = "+";
    for (int i = 0; i < 5; i++)
    {
        a = a + "A";
        Console.WriteLine(string.Format("Loop-{0}-{1} ThreadId:{2} {3}", i, a, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        Thread.Sleep(2000);
    }

    Console.WriteLine(string.Format("End Click ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    return;
}

private void SubThreadMethod()
{
    Console.WriteLine(string.Format("Begin SubThreadMethod ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    button1.Invoke(new Del(invokeMethod));
    //button1.BeginInvoke(new Del(DelMethod));
    Console.WriteLine(string.Format("End BeginInvoke ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    string b = "-";
    for (int i = 0; i < 7; i++)
    {
        b = b + "B";
        Console.WriteLine(string.Format("Loop-{0}-{1} ThreadId:{2} {3}", i, b, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        Thread.Sleep(1300);
    }

    Console.WriteLine(string.Format("End SubThreadMethod ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    return;
}

public delegate void Del();

private void DelMethod()
{
    Console.WriteLine(string.Format("Begin DelMethod ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    this.textBox1.Text = DateTime.Now.ToString();
    Console.WriteLine(string.Format("End DelMethod ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    return;
}

        在子线程中运行Control.Invoke(Delegate),会立即将Delegate封装的操作交给主线程运行。

        此时,控制权会立即从子线程交给主线程,只有当主线程运行完Delegate封装的操作后,才会再接着运行子线程中Control.Invoke(Delegate)语句后面的操作;而在主线程运行完Delegate封装的操作之前,子线程(Control.Invoke(Delegate)语句后面的操作)会被阻塞。

1.3、Control.BeginInvoke(Delegate)

        在子线程中运行Control.BeginBeginInvoke(Delegate),也会将Delegate封装的操作交给主线程运行。

        此时,子线程(Control.Invoke(Delegate)语句后面的操作)不会被阻塞。至于主线程、子线程的执行顺序,是由调度算法控制的,在用户看来是不确定的。

1.4、总结

        Control.Invoke(Delegate)、Control.BeginBeginInvoke(Delegate),都会将Delegate封装的操作交给主线程运行,而当Delegate封装的操作是刷新Control时,就不会抛出“跨线程访问控件”的运行时异常了。

五、参考

C# Winform的多线程(含Gif讲解)_白粥行的博客-CSDN博客_c# winform 多线程

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值