C# Proper processing method for updating UI interface across threads, and exploration of friendly interface exit mechanism

This article mainly talks about a situation that is often encountered in C# form programs, that is, when exiting the form, an exit exception occurs.

Contact the author and how to join the group (activation code will be issued in the group): Cooperation - HslCommunication official website 

Welcome to technical discussion

Let's first take a look at a typical scenario, where data is regularly obtained from a PLC or a remote server, and then the labels on the interface are updated, basically in real time. We can simplify the model into a form and open a thread to read it regularly.

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

    public partial class Form1 : Form

    {

        public Form1( )

        {

            InitializeComponent( );

        }

        private void Form1_Load( object sender, EventArgs e )

        {

            thread = new System.Threading.Thread( new System.Threading.ThreadStart( ThreadCapture ) );

            thread.IsBackground = true;

            thread.Start( );

        }

        private void ThreadCapture( )

        {

            System.Threading.Thread.Sleep( 200 );

            while (true)

            {

                // 我们假设这个数据是从PLC或是远程服务器获取到的,因为可能比较耗时,我们放在了后台线程获取,并且处于一直运行的状态

                // 我们还假设获取数据的频率是200ms一次,然后把数据显示出来

                int data = random.Next( 1000 );

                // 接下来是跨线程的显示

                Invoke( new Action( ( ) =>

                 {

                     label1.Text = data.ToString( );

                 } ) );

                System.Threading.Thread.Sleep( 200 );

            }

        }

        private System.Threading.Thread thread;

        private Random random = new Random( );

    }

}  

We are very likely to write this. When we click on the window, the following exception will appear.

The root cause of this problem is that when you click to close the form, all components of the form begin to release resources, but the thread has not been closed immediately, so let's optimize the appealed code.

1. Optimize the continuous creation of delegate instances


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

private void ThreadCapture( )

{

    showInfo = new Action<string>( m =>

    {

        label1.Text = m;

    } );

    System.Threading.Thread.Sleep( 200 );

    while (true)

    {

        // 我们假设这个数据是从PLC或是远程服务器获取到的,因为可能比较耗时,我们放在了后台线程获取,并且处于一直运行的状态

        // 我们还假设获取数据的频率是200ms一次,然后把数据显示出来

        int data = random.Next( 1000 );

        // 接下来是跨线程的显示

        Invoke( showInfo, data.ToString( ) );

        System.Threading.Thread.Sleep( 200 );

    }

}

private Action<string> showInfo;

private System.Threading.Thread thread;

private Random random = new Random( );

 This avoids the frequent creation of delegate instances every 200ms. These delegate instances consume memory when the GC recycles data. Users may not feel it, but good development habits use less memory to execute a lot of things.

 I just started thinking about how to avoid exiting exceptions. Since it reports an error of null, I will add a judgment.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

private void ThreadCapture( )

{

    showInfo = new Action<string>( m =>

    {

        label1.Text = m;

    } );

    System.Threading.Thread.Sleep( 200 );

    while (true)

    {

        // 我们假设这个数据是从PLC或是远程服务器获取到的,因为可能比较耗时,我们放在了后台线程获取,并且处于一直运行的状态

        // 我们还假设获取数据的频率是200ms一次,然后把数据显示出来

        int data = random.Next( 1000 );

        // 接下来是跨线程的显示

        if(IsHandleCreated && !IsDisposed) Invoke( showInfo, data.ToString( ) );

        System.Threading.Thread.Sleep( 200 );

    }

}

  

When displaying the interface, determine whether the handle is created and whether it is currently released. Abnormalities occur less frequently, but they still occur. The following provides a simple and crude idea. Since the error report has been released, I will catch the exception.

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

private void ThreadCapture( )

{

    showInfo = new Action<string>( m =>

    {

        label1.Text = m;

    } );

    System.Threading.Thread.Sleep( 200 );

    while (true)

    {

        // 我们假设这个数据是从PLC或是远程服务器获取到的,因为可能比较耗时,我们放在了后台线程获取,并且处于一直运行的状态

        // 我们还假设获取数据的频率是200ms一次,然后把数据显示出来

        int data = random.Next( 1000 );

        try

        {

            // 接下来是跨线程的显示

            if (IsHandleCreated && !IsDisposed) Invoke( showInfo, data.ToString( ) );

        }

        catch (ObjectDisposedException)

        {

            break;

        }

        catch

        {

            throw;

        }

        System.Threading.Thread.Sleep( 200 );

    }

}

  

This is a simple and crude solution, but I still feel that it is not very good, so instead of using try..catch, I changed my mind and added a mark myself to indicate whether the form is displayed. When the form is closed, reset this mark

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

private void ThreadCapture( )

{

    showInfo = new Action<string>( m =>

    {

        label1.Text = m;

    } );

    isWindowShow = true;

    System.Threading.Thread.Sleep( 200 );

    while (true)

    {

        // 我们假设这个数据是从PLC或是远程服务器获取到的,因为可能比较耗时,我们放在了后台线程获取,并且处于一直运行的状态

        // 我们还假设获取数据的频率是200ms一次,然后把数据显示出来

        int data = random.Next( 1000 );

        // 接下来是跨线程的显示

        if (isWindowShow) Invoke( showInfo, data.ToString( ) );

        else break;

        System.Threading.Thread.Sleep( 200 );

    }

}

private bool isWindowShow = false;

private Action<string> showInfo;

private System.Threading.Thread thread;

private Random random = new Random( );

private void Form1_FormClosing( object sender, FormClosingEventArgs e )

{

    isWindowShow = false;

}

The whole program has become like this. Even if we test it many times, it still happens frequently. At this time, we need to consider deeper things. What should I do when my program exits? Turn off the collection thread and stop refreshing. Only then can you truly exit.

At this time, synchronization technology is needed, and we continue to transform

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

    private void ThreadCapture( )

    {

        showInfo = new Action<string>( m =>

        {

            label1.Text = m;

        } );

        isWindowShow = true;

        System.Threading.Thread.Sleep( 200 );

        while (true)

        {

            // 我们假设这个数据是从PLC或是远程服务器获取到的,因为可能比较耗时,我们放在了后台线程获取,并且处于一直运行的状态

            // 我们还假设获取数据的频率是200ms一次,然后把数据显示出来

            int data = random.Next( 1000 );

            // 接下来是跨线程的显示,并检测窗体是否关闭

            if (isWindowShow) Invoke( showInfo, data.ToString( ) );

            else break;

            System.Threading.Thread.Sleep( 200 );

            // 再次检测窗体是否关闭

            if (!isWindowShow) break;

        }

        // 通知主界面是否准备退出

        resetEvent.Set( );

    }

    private System.Threading.AutoResetEvent resetEvent = new System.Threading.AutoResetEvent( false );

    private bool isWindowShow = false;

    private Action<string> showInfo;

    private System.Threading.Thread thread;

    private Random random = new Random( );

    private void Form1_FormClosing( object sender, FormClosingEventArgs e )

    {

        isWindowShow = false;

        resetEvent.WaitOne( );

    }

}

Based on the idea, we wrote this code. After running it for a while, the result is stuck. The reason is that when you just click to exit, if Invoke is found to be displayed, a deadlock will form. So the first method is to change the following mechanism.

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

private void ThreadCapture( )

{

    showInfo = new Action<string>( m =>

    {

        label1.Text = m;

    } );

    isWindowShow = true;

    System.Threading.Thread.Sleep( 200 );

    while (true)

    {

        // 我们假设这个数据是从PLC或是远程服务器获取到的,因为可能比较耗时,我们放在了后台线程获取,并且处于一直运行的状态

        // 我们还假设获取数据的频率是200ms一次,然后把数据显示出来

        int data = random.Next( 1000 );

        // 接下来是跨线程的显示,并检测窗体是否关闭

        if (isWindowShow) BeginInvoke( showInfo, data.ToString( ) );

        else break;

        System.Threading.Thread.Sleep( 200 );

        // 再次检测窗体是否关闭

        if (!isWindowShow) break;

    }

    // 通知主界面是否准备退出

    resetEvent.Set( );

}

private System.Threading.AutoResetEvent resetEvent = new System.Threading.AutoResetEvent( false );

private bool isWindowShow = false;

private Action<string> showInfo;

private System.Threading.Thread thread;

private Random random = new Random( );

private void Form1_FormClosing( object sender, FormClosingEventArgs e )

{

    isWindowShow = false;

    resetEvent.WaitOne( );

}

Changing to an asynchronous mechanism when Invoke can solve this problem, but the BeginInvoke method is not a particularly safe way, and we may be stuck for a while when exiting. We need to think about whether there is a better way. mechanism.

Wouldn't it be better if we made a waiting exit window? Without getting stuck in the main window, you can exit perfectly. Let’s create a new small window.

Remove the borders, the interface is as simple as that, and then modify the code

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

public partial class FormQuit : Form

{

    public FormQuit( Action action )

    {

        InitializeComponent( );

        this.action = action;

    }

    private void FormQuit_Load( object sender, EventArgs e )

    {

    }

    // 退出前的操作

    private Action action;

    private void FormQuit_Shown( object sender, EventArgs e )

    {

        // 调用操作

        action.Invoke( );

        Close( );

    }

}

 We just need to pass the operation before exit to the exit window.

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

    private void ThreadCapture( )

    {

        showInfo = new Action<string>( m =>

        {

            label1.Text = m;

        } );

        isWindowShow = true;

        System.Threading.Thread.Sleep( 200 );

        while (true)

        {

            // 我们假设这个数据是从PLC或是远程服务器获取到的,因为可能比较耗时,我们放在了后台线程获取,并且处于一直运行的状态

            // 我们还假设获取数据的频率是200ms一次,然后把数据显示出来

            int data = random.Next( 1000 );

            // 接下来是跨线程的显示,并检测窗体是否关闭

            if (isWindowShow) Invoke( showInfo, data.ToString( ) );

            else break;

            System.Threading.Thread.Sleep( 200 );

            // 再次检测窗体是否关闭

            if (!isWindowShow) break;

        }

        // 通知主界面是否准备退出

        resetEvent.Set( );

    }

    private System.Threading.AutoResetEvent resetEvent = new System.Threading.AutoResetEvent( false );

    private bool isWindowShow = false;

    private Action<string> showInfo;

    private System.Threading.Thread thread;

    private Random random = new Random( );

    private void Form1_FormClosing( object sender, FormClosingEventArgs e )

    {

        FormQuit formQuit = new FormQuit( new Action(()=>

        {

            isWindowShow = false;

            resetEvent.WaitOne( );

        } ));

        formQuit.ShowDialog( );

    }

}

At this point, your program will no longer have the exception that the above object has been released. The exit form will display an irregular time, 0-200 milliseconds. In order to have an obvious lingering effect, we sleep for 200ms more, so that we complete the final program, as follows:

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

public partial class Form1 : Form

{

    public Form1( )

    {

        InitializeComponent( );

    }

    private void Form1_Load( object sender, EventArgs e )

    {

        thread = new System.Threading.Thread( new System.Threading.ThreadStart( ThreadCapture ) );

        thread.IsBackground = true;

        thread.Start( );

    }

    private void ThreadCapture( )

    {

        showInfo = new Action<string>( m =>

        {

            label1.Text = m;

        } );

        isWindowShow = true;

        System.Threading.Thread.Sleep( 200 );

        while (true)

        {

            // 我们假设这个数据是从PLC或是远程服务器获取到的,因为可能比较耗时,我们放在了后台线程获取,并且处于一直运行的状态

            // 我们还假设获取数据的频率是200ms一次,然后把数据显示出来

            int data = random.Next( 1000 );

            // 接下来是跨线程的显示,并检测窗体是否关闭

            if (isWindowShow) Invoke( showInfo, data.ToString( ) );

            else break;

            System.Threading.Thread.Sleep( 200 );

            // 再次检测窗体是否关闭

            if (!isWindowShow) {System.Threading.Thread.Sleep(50);break;}

        }

        // 通知主界面是否准备退出

        resetEvent.Set( );

    }

    private System.Threading.AutoResetEvent resetEvent = new System.Threading.AutoResetEvent( false );

    private bool isWindowShow = false;

    private Action<string> showInfo;

    private System.Threading.Thread thread;

    private Random random = new Random( );

    private void Form1_FormClosing( object sender, FormClosingEventArgs e )

    {

        FormQuit formQuit = new FormQuit( new Action(()=>

        {

            System.Threading.Thread.Sleep( 200 );

            isWindowShow = false;

            resetEvent.WaitOne( );

        } ));

        formQuit.ShowDialog( );

    }

}

Guess you like

Origin blog.csdn.net/weixin_45499836/article/details/124042818