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

目录

一、前言

二、背景

三、Delegate对象的Invoke/BeginInvoke/EndInvoke

1、基于[需求1]

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

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

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

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

1.5、总结

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、Control.InvokeRequired

1.5、总结

五、参考


一、前言

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

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

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

二、背景

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

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

private void button1_Click(object sender, EventArgs e)
{
    Console.WriteLine(string.Format("Begin Click ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    Task task = new Task(new Action(SubThreadMethod));
    task.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()));
    Task.Delay(10000);
    Console.WriteLine(string.Format("End SubThread ThreadId:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    return;
}

        需要将“输出类型”从“Windows应用程序”改为“控制台应用程序”,然后双击生成的exe。

1.4、通过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.3]

1.5、总结

        对比2、3、4发现,Delegate对象.BeginInvoke()实质上是Thread、Task,创建了一个子线程,然后在子线程中运行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;
}

   

        第一种写法:用lambda表达式。

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

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

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

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

ExeMethod_Delegate exeMethod_Delegate = null;

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

    exeMethod_Delegate = new ExeMethod_Delegate(ExeMethod);
    for (int i = 3; i < 11; i++)
    {
        Console.WriteLine(string.Format("{0}-Begin BeginInvoke ThreadId:{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        exeMethod_Delegate.BeginInvoke(10 * i, 1000 * i, new AsyncCallback(ExeMethod_Callback), 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 ExeMethod_Delegate(int num, int ms);

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

    return num * num;
}

private void ExeMethod_Callback(IAsyncResult result)
{
    int i = (int)result.AsyncState;

    Console.WriteLine(string.Format("{0}-Begin Callback ThreadId:{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    int product = exeMethod_Delegate.EndInvoke(result);
    Console.WriteLine(string.Format("{0}-End Callback 结果:{1} ThreadId:{2} {3}", i, product, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    return;
}

        第二种写法:ExeMethod_Delegate exeMethod_Delegate作为类的成员变量,可以在ExeMethod_Callback回调中,直接用到exeMethod_Delegate。

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

        ExeMethod_Delegate exeMethod_Delegate = new ExeMethod_Delegate(ExeMethod);
        for (int i = 3; i < 11; i++)
        {
            Console.WriteLine(string.Format("{0}-Begin BeginInvoke ThreadId:{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
            //exeMethod_Delegate.BeginInvoke(10 * i, 1000 * i, new AsyncCallback(ExeMethod_Callback), exeMethod_Delegate);
            exeMethod_Delegate.BeginInvoke(10 * i, 1000 * i, ExeMethod_Callback, (exeMethod_Delegate, 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 ExeMethod_Delegate(int num, int ms);

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

        return num * num;
    }

    private void ExeMethod_Callback(IAsyncResult result)
    {
        (ExeMethod_Delegate, int) res = ((ExeMethod_Delegate, int))result.AsyncState;
        int i = res.Item2;

        Console.WriteLine(string.Format("{0}-Begin Callback ThreadId:{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        int product = res.Item1.EndInvoke(result);
        Console.WriteLine(string.Format("{0}-End Callback 乘积:{1} ThreadId:{2} {3}", i, product, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

        return;
    }
Begin Click ThreadId:1 2024/8/2 13:36:00
3-Begin BeginInvoke ThreadId:1 2024/8/2 13:36:00
3-End BeginInvoke ThreadId:1 2024/8/2 13:36:00
4-Begin BeginInvoke ThreadId:1 2024/8/2 13:36:00
4-End BeginInvoke ThreadId:1 2024/8/2 13:36:00
5-Begin BeginInvoke ThreadId:1 2024/8/2 13:36:00
5-End BeginInvoke ThreadId:1 2024/8/2 13:36:00
6-Begin BeginInvoke ThreadId:1 2024/8/2 13:36:00
6-End BeginInvoke ThreadId:1 2024/8/2 13:36:00
7-Begin BeginInvoke ThreadId:1 2024/8/2 13:36:00
7-End BeginInvoke ThreadId:1 2024/8/2 13:36:00
8-Begin BeginInvoke ThreadId:1 2024/8/2 13:36:00
8-End BeginInvoke ThreadId:1 2024/8/2 13:36:00
9-Begin BeginInvoke ThreadId:1 2024/8/2 13:36:00
9-End BeginInvoke ThreadId:1 2024/8/2 13:36:00
10-Begin BeginInvoke ThreadId:1 2024/8/2 13:36:00
10-End BeginInvoke ThreadId:1 2024/8/2 13:36:00
End Click ThreadId:1 2024/8/2 13:36:00
3-Begin ExeMethod ThreadId:3 2024/8/2 13:36:00
4-Begin ExeMethod ThreadId:6 2024/8/2 13:36:00
8-Begin ExeMethod ThreadId:4 2024/8/2 13:36:00
6-Begin ExeMethod ThreadId:8 2024/8/2 13:36:00
5-Begin ExeMethod ThreadId:7 2024/8/2 13:36:00
10-Begin ExeMethod ThreadId:10 2024/8/2 13:36:00
9-Begin ExeMethod ThreadId:5 2024/8/2 13:36:00
7-Begin ExeMethod ThreadId:11 2024/8/2 13:36:00
3-End ExeMethod ThreadId:3 2024/8/2 13:36:03
3-Begin Callback ThreadId:3 2024/8/2 13:36:03
3-End Callback 乘积:900 ThreadId:3 2024/8/2 13:36:03
4-End ExeMethod ThreadId:6 2024/8/2 13:36:04
4-Begin Callback ThreadId:6 2024/8/2 13:36:04
4-End Callback 乘积:1600 ThreadId:6 2024/8/2 13:36:04
5-End ExeMethod ThreadId:7 2024/8/2 13:36:05
5-Begin Callback ThreadId:7 2024/8/2 13:36:05
5-End Callback 乘积:2500 ThreadId:7 2024/8/2 13:36:05
6-End ExeMethod ThreadId:8 2024/8/2 13:36:06
6-Begin Callback ThreadId:8 2024/8/2 13:36:06
6-End Callback 乘积:3600 ThreadId:8 2024/8/2 13:36:06
7-End ExeMethod ThreadId:11 2024/8/2 13:36:07
7-Begin Callback ThreadId:11 2024/8/2 13:36:07
7-End Callback 乘积:4900 ThreadId:11 2024/8/2 13:36:07
8-End ExeMethod ThreadId:4 2024/8/2 13:36:08
8-Begin Callback ThreadId:4 2024/8/2 13:36:08
8-End Callback 乘积:6400 ThreadId:4 2024/8/2 13:36:08
9-End ExeMethod ThreadId:5 2024/8/2 13:36:09
9-Begin Callback ThreadId:5 2024/8/2 13:36:09
9-End Callback 乘积:8100 ThreadId:5 2024/8/2 13:36:09
10-End ExeMethod ThreadId:10 2024/8/2 13:36:10
10-Begin Callback ThreadId:10 2024/8/2 13:36:10
10-End Callback 乘积:10000 ThreadId:10 2024/8/2 13:36:10

        第三种写法:可以将ExeMethod_Delegate委托本身作为参数,传给Delegate.BeginInvoke()函数,然后在ExeMethod_Callback回调中用到该委托。

        从Delegate.BeginInvoke()传入的参数、ExeMethod()函数运行完return的结果,都封装在ExeMethod_Callback()回调函数的IAsyncResult输入参数中。前者通过IAsyncResult.AsyncState获取,后者通过Delegate.EndInvoke()获取。

        可以看出,ExeMethod函数和ExeMethod_Callback回调,都是在同一个子线程完成的

        了解了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("+{0}-Loop-{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 CalcDel(ExeMethod));
    //button1.BeginInvoke(new CalcDel(ExeMethod));
    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("={0}-Loop-{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 CalcDel();

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

    return;
}

        在子线程中运行Control.Invoke(Delegate),子线程会被阻塞,子线程会等待主线程运行完Delegate封装的操作后,才可继续执行子线程。但是并不意味着,主线程会立即运行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.InvokeRequired

private void Form1_Load(object sender, EventArgs e)
{
    Thread subThread = new Thread(CrossThreadFlush);
    subThread.IsBackground = true;
    subThread.Start();
 
    return;
}
 
private void CrossThreadFlush()
{
    while (true)
    {
        Thread.Sleep(2000);
        FlushText();
    }
    
    return;
}

private delegate void FlushText_Delegate();
private void FlushText()
{
    if (this.textBox1.InvokeRequired)
    {
        this.Invoke(new FlushText_Delegate(FlushText));
    }
    else
    {
        this.textBox1.Text = DateTime.Now.ToString();
    }
 
    return;
}

        InvokeRequired为true,表明当前调用Control的成员方法/成员变量的线程,与创建Control的线程,不是同一个线程,不能跨线程访问Control;为false,表明是同一个线程。

        【1.4的代码,有待测试。。。】

1.5、总结

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

五、参考

C# InvokeRequired和Invoke-优快云博客

WinForm二三事(二)异步操作 - 风雨者2 - 博客园

C# Winform的多线程(含Gif讲解)_winform 多线程-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值