C# Método de procesamiento adecuado para actualizar la interfaz de usuario entre subprocesos y exploración de un mecanismo de salida de interfaz amigable

Este artículo habla principalmente de una situación que se encuentra a menudo en los programas de formulario C#, es decir, al salir del formulario, se produce una excepción de salida.

Contacte con el autor y cómo unirse al grupo (el código de activación se emitirá en el grupo): Cooperación - Sitio web oficial de HslCommunication 

Bienvenido a la discusión técnica

Primero echemos un vistazo a un escenario típico, donde los datos se obtienen regularmente de un PLC o un servidor remoto, y luego las etiquetas en la interfaz se actualizan, básicamente en tiempo real. Podemos simplificar el modelo en un formulario y abrir un hilo para leerlo periódicamente.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

dieciséis

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( );

    }

}  

Es muy probable que escribamos así, al hacer clic en la ventana aparecerá la siguiente excepción

La causa principal de este problema es que cuando hace clic para cerrar el formulario, todos los componentes del formulario comienzan a liberar recursos, pero el hilo no se cierra de inmediato, así que optimicemos el código apelado.

1. Optimice la creación continua de instancias de delegados.


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

dieciséis

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( );

 Esto evita la creación frecuente de instancias de delegados cada 200 ms. Estas instancias de delegados consumen memoria cuando el GC recicla datos. Los usuarios pueden no sentirlo, pero los buenos hábitos de desarrollo usan menos memoria para ejecutar muchas cosas.

 Recién comencé a pensar en cómo evitar salir de la excepción. Dado que informa un error nulo, agregaré un juicio.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

dieciséis

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 );

    }

}

  

Al mostrar la interfaz, determine si el identificador está creado y si está publicado actualmente. Las anomalías ocurren con menos frecuencia, pero aún ocurren. Lo siguiente proporciona una idea simple y cruda: dado que se publicó el informe de error, detectaré la excepción.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

dieciséis

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 );

    }

}

  

Esta es una solución simple y tosca, pero todavía siento que no es muy buena, así que en lugar de usar try..catch, cambié de opinión y agregué una marca para indicar si se muestra el formulario. Cuando el formulario esté cerrado, restablezca esta marca.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

dieciséis

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;

}

Todo el programa se ha vuelto así. Incluso si lo probamos muchas veces, todavía sucede con frecuencia. En este momento, debemos considerar cosas más profundas. ¿Qué debo hacer cuando mi programa salga? Apague el hilo de recopilación y deje de actualizar. Sólo entonces podrá salir realmente.

En este momento, se necesita tecnología de sincronización y continuamos transformando

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

dieciséis

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( );

    }

}

Basándonos en la idea, escribimos este código. Después de ejecutarlo por un tiempo, el resultado está bloqueado, la razón es que cuando simplemente hace clic para salir, si se muestra Invoke, se producirá un punto muerto, por lo que el primer método es cambiar el siguiente mecanismo.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

dieciséis

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( );

}

Cambiar a un mecanismo asincrónico cuando Invoke puede resolver este problema, pero el método BeginInvoke no es una forma particularmente segura y es posible que nos quedemos atascados por un tiempo al salir. Necesitamos pensar si hay una mejor manera.

¿No sería mejor si hiciéramos una ventana de salida de espera? Sin quedarte atascado en la ventana principal, puedes salir perfectamente, creemos una nueva ventana pequeña.

Elimina los bordes, la interfaz es así de simple y luego modifica el código.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

dieciséis

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( );

    }

}

 Solo necesitamos pasar la operación antes de salir a la ventana de salida.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

dieciséis

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( );

    }

}

En este punto, su programa ya no tendrá la excepción de que el objeto anterior ha sido liberado. El formulario de salida mostrará un tiempo irregular, de 0 a 200 milisegundos. Para tener un efecto persistente obvio, dormimos 200 ms más, para completar el programa final, de la siguiente manera:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

dieciséis

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( );

    }

}

Supongo que te gusta

Origin blog.csdn.net/weixin_45499836/article/details/124042818
Recomendado
Clasificación