Ejemplo de como se actualiza un control ProgressBar de un formulario WPF usando un subproceso Task

Descripción general:

Ejemplo de como se actualiza un control ProgressBar de un formulario WPF usando un subproceso Task

ProgressBar en una ventana WPF

En el moderno XAML, el control ProgressBar no funciona como lo hacia en los formularios. el problema es que el código que mueve el valor del control ProgressBar esta en otro hilo, y por razones de seguridad no permite su actualización, lo que ocurre es que aparentemente no pasa nada hasta que acaba el proceso y entonces aparece el control ProgressBar totalmente lleno

Este comportamiento se puede solucionar de varias formas pero todas ellas implican trabajar con hilos.

La situación de ejemplo es la siguiente:

Suponemos un cronometro inverso, que cuente los segundos que faltan para que acabe un proceso. De esta forma podremos usar los segundos que faltan para actualizar la propiedad [value] del ProgressBar.

Este problema existe también con los demás controles que muestran información, Label, TextBox, etc, ya que se comportan de la misma manera que el ProgressBar. Si queremos actualizarlos TAMBIÉN tendremos que usar hilos

En este ejemplo mostramos en un control TextBox, una cadena que indica en minutos y segundos el tiempo que falta para que se acabe el proceso

Cuando se ejecuta el programa Muestra una ventana con tres botones [Aceptar], [Cancelar],[Terminar] una barra de progreso y un TextBox (dentro de la barra) que muestra el tiempo que falta


↑↑↑

Imagen de la pantalla de este formulario XAML

Imagen del formulario ejecutandose

↑↑↑

Código XAML


<Window  x:Class="WindowPruebaProgressBarConTask"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:PausadorNet2023"
        mc:Ignorable="d"
        Title="Window Prueba ProgressBar Con Task"
        Height="150" Width="500"    
        ResizeMode="CanResizeWithGrip">

    <Window.Resources>

        <!-- ++++++++++++++++++++++++++++++++++++++++++  -->
        <!-- Comandos personalizados para los botones inferiores [Ejecutar], [Cancelar], [Terminar]   -->

        <!--El comando personalizado para el botón  [Aceptar] -->
        <RoutedUICommand  x:Key="RCBotonEjecutarCmd" 
                         Text="Acciones que se producen al pulsar el botón [Aceptar] del Grupo de botones [Ejecutar], [Cancelar], [Terminar]">
        </RoutedUICommand>
        <!--El comando personalizado para el botón  [Cancelar] -->
        <RoutedUICommand  x:Key="RCBotonCancelarCmd" 
                         Text="Acciones que se producen al pulsar el botón [Cancelar] del Grupo de botones [Ejecutar], [Cancelar], [Terminar]">
        </RoutedUICommand>
    </Window.Resources>

    <Window.CommandBindings>

        <!-- ++++++++++++++++++++++++++++++++++++++++++  -->
        <!-- Los botones inferiores  / Ejecutar / Cancelar / Salir -->

        <!--El comando personalizado para el botón  [Ejecutar] -->
        <CommandBinding  Command="{StaticResource RCBotonEjecutarCmd}"  
                        Executed="BotonEjecutarCommandBinding_Executed"  
                        CanExecute="BotonEjecutarCommandBinding_CanExecute"/>

        <!--El comando personalizado para el botón  [Cancelar] -->
        <CommandBinding  Command="{StaticResource RCBotonCancelarCmd}"  
                        Executed="BotonCancelarCommandBinding_Executed"  
                        CanExecute="BotonCancelarCommandBinding_CanExecute"/>

        <!--El comando personalizado para el botón  [Salir / Terminar] -->
        <CommandBinding  Command="ApplicationCommands.Close"  
                        Executed="BotonCloseCommandBinding_Executed"  
                        CanExecute="BotonCloseCommandBinding_CanExecute"/>

    </Window.CommandBindings>

    <Window.InputBindings>

        <!-- Atajo de teclado para que se ejecute  Botón [Salir]  -->
        <KeyBinding  Key="Esc"  Modifiers=""      Command="ApplicationCommands.Close"  />
        <KeyBinding  Key="F4"   Modifiers="Alt"   Command="ApplicationCommands.Close"  />

        <!-- Atajo de teclado para que se ejecute  Botón [Ejecutar] / [Cancelar] -->
        <KeyBinding  Key="F5"   Modifiers=""      Command="{StaticResource RCBotonEjecutarCmd}"  />
        <KeyBinding  Key="F5"   Modifiers="Ctrl"  Command="{StaticResource RCBotonCancelarCmd}"  />

    </Window.InputBindings>

    <Grid>

        <Grid.RowDefinitions>
            <RowDefinition  Height="auto"/>
            <RowDefinition  Height="*"/>
            <RowDefinition  Height="auto"/>
        </Grid.RowDefinitions>

        <Grid  Grid.Row="0" 
                 x:Name="GridProgressBarConPorcentaje" 
                 HorizontalAlignment="Stretch" VerticalAlignment="Top"  
                 Height="25" 
                 Margin="0,10,0,0" >

            <ProgressBar  x:Name="pbStatus"                                  
                     HorizontalAlignment="Stretch" VerticalAlignment="Stretch"  
                         
                     Value="{Binding Value, 
                             RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" Foreground="#FF09F5BA" >
                <ProgressBar.Background>
                    <ImageBrush/>
                </ProgressBar.Background>
            </ProgressBar>

            <!--<TextBlock   HorizontalAlignment="Center" VerticalAlignment="Center" 
                     TextWrapping="NoWrap" 
                     FontFamily="Consolas"
                     Text="{Binding Value, ElementName=pbStatus, StringFormat=\{0:00.00\} %}" />-->

            <TextBlock  x:Name="TextBoxCronometro" HorizontalAlignment="Center"  VerticalAlignment="Center" 
                     FontFamily="Consolas"
                     TextWrapping="Wrap" Text="Cancelado!"  Width="75" Opacity="0.5"/>

        </Grid>

        <!-- Grid con los tres botones [Ejecutar], [Cancelar],[ Terminar]  -->
        <Grid   Grid.Row="2" 
               Name="GridPanelTresBotonesOkCancelSalirComImagenes" 
               HorizontalAlignment="Center"  
               VerticalAlignment="Bottom" 
               Grid.IsSharedSizeScope="True"
               Margin="0,10,10,0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition  x:Uid="ColumnDefinition1"  SharedSizeGroup="ButtonsAceptarCancelar"  Width="120"/>
                <ColumnDefinition  x:Uid="ColumnDefinition3"  SharedSizeGroup="ButtonsAceptarCancelar" />
                <ColumnDefinition  x:Uid="ColumnDefinition3"  SharedSizeGroup="ButtonsAceptarCancelar" />
            </Grid.ColumnDefinitions>

            <Button  Grid.Column="0"
                Name="ButtonEjecutar"  
                Command="{StaticResource RCBotonEjecutarCmd}"  
                IsCancel="False"
                IsEnabled="true" >
                <Button.ToolTip>
                    <StackPanel  Orientation="Vertical">
                        <TextBlock  Text=" Ejecutar" />
                        <TextBlock  Text=" Ejecuta (pone en marcha) el proceso" />
                        <TextBlock  Text=" Atajo de teclado [F5] " />
                    </StackPanel>
                </Button.ToolTip>
                <StackPanel   Orientation="Horizontal"  HorizontalAlignment="Left" VerticalAlignment="Center">
                    <!-- VS2019 Image Library / Status Run -->
                    <Viewbox  Width="16" Height="16" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
                        <Rectangle  Width="16" Height="16">
                            <Rectangle.Fill>
                                <DrawingBrush>
                                    <DrawingBrush.Drawing>
                                        <DrawingGroup>
                                            <DrawingGroup.Children>
                                                <GeometryDrawing  Brush="#FFF6F6F6" Geometry="F1M0,8C0,3.582 3.582,0 8,0 12.418,0 16,3.582 16,8 16,12.418 12.418,16 8,16 3.582,16 0,12.418 0,8" />
                                                <GeometryDrawing  Brush="#FF329932" Geometry="F1M6,12L6,4 12,8z M8,1C4.135,1 1,4.134 1,8 1,11.865 4.135,15 8,15 11.865,15 15,11.865 15,8 15,4.134 11.865,1 8,1" />
                                                <GeometryDrawing  Brush="#FFFFFFFF" Geometry="F1M6,4L12,8 6,12z" />
                                            </DrawingGroup.Children>
                                        </DrawingGroup>
                                    </DrawingBrush.Drawing>
                                </DrawingBrush>
                            </Rectangle.Fill>
                        </Rectangle>
                    </Viewbox>
                    <Label   Content="Ejecutar"    />
                </StackPanel>
            </Button>

            <Button  Grid.Column="1"
                Name="ButtonCancelar"
                Command="{StaticResource RCBotonCancelarCmd}"
                IsCancel="False"
                IsEnabled="True">
                <Button.ToolTip>
                    <StackPanel  Orientation="Vertical">
                        <TextBlock  Text=" Cancelar" />
                        <TextBlock  Text=" Detiene el proceso y vuelve a la situación inicial " />
                        <TextBlock  Text=" Los datos internos de trabajo se pierden" />
                        <TextBlock  Text=" Para detener la ejecución del programa primero" />
                        <TextBlock  Text=" se pulsa el botón [Cancelar] y a continuación [Salir]" />
                        <TextBlock  Text=" Atajo de teclado  [Ctrl + F5] " />
                    </StackPanel>
                </Button.ToolTip>
                <StackPanel   Orientation="Horizontal"  HorizontalAlignment="Left" VerticalAlignment="Center">
                    <!-- VS2019 Image Library / Status Stop -->
                    <Viewbox  Width="16" Height="16" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
                        <Rectangle  Width="16" Height="16">
                            <Rectangle.Fill>
                                <DrawingBrush>
                                    <DrawingBrush.Drawing>
                                        <DrawingGroup>
                                            <DrawingGroup.Children>
                                                <GeometryDrawing  Brush="#00FFFFFF" Geometry="F1M16,16L0,16 0,0 16,0z" />
                                                <GeometryDrawing  Brush="#FFF6F6F6" Geometry="F1M0,8C0,3.582 3.582,0 8,0 12.418,0 16,3.582 16,8 16,12.418 12.418,16 8,16 3.582,16 0,12.418 0,8" />
                                                <GeometryDrawing  Brush="#FFE41400" Geometry="F1M11,11L5,11 5,5 11,5z M8,1C4.135,1 1,4.134 1,8 1,11.865 4.135,15 8,15 11.865,15 15,11.865 15,8 15,4.134 11.865,1 8,1" />
                                                <GeometryDrawing  Brush="#FFFFFFFF" Geometry="F1M11,11L5,11 5,5 11,5z" />
                                            </DrawingGroup.Children>
                                        </DrawingGroup>
                                    </DrawingBrush.Drawing>
                                </DrawingBrush>
                            </Rectangle.Fill>
                        </Rectangle>
                    </Viewbox>
                    <Label   Content="Cancelar"    />
                </StackPanel>
            </Button>

            <Button  Grid.Column="2"
                Name="ButtonTerminar"
                Command="ApplicationCommands.Close"  
                IsCancel="True"
                IsEnabled="True" >
                <Button.ToolTip>
                    <StackPanel  Orientation="Vertical">
                        <TextBlock  Text=" Salir" />
                        <TextBlock  Text=" Termina la ejecución del programa " />
                        <TextBlock  Text=" y cierra esta ventana" />
                        <TextBlock  Text=" Atajo de teclado [Alt + F4] " />
                        <TextBlock  Text=" Atajo de teclado [Esc] " />
                    </StackPanel>
                </Button.ToolTip>
                <StackPanel   Orientation="Horizontal"  HorizontalAlignment="Left" VerticalAlignment="Center">
                    <!-- VS2019 Image Library / Exit -->
                    <Viewbox  Width="16" Height="16" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
                        <Rectangle  Width="16" Height="16">
                            <Rectangle.Fill>
                                <DrawingBrush>
                                    <DrawingBrush.Drawing>
                                        <DrawingGroup>
                                            <DrawingGroup.Children>
                                                <GeometryDrawing  Brush="#00FFFFFF" Geometry="F1M16,16L0,16 0,0 16,0z" />
                                                <GeometryDrawing  Brush="#FFF6F6F6" Geometry="F1M9.5859,10L8.9999,10.586 8.9999,11.445C8.4099,11.789 7.7319,12 6.9999,12 4.7909,12 2.9999,10.209 2.9999,8 2.9999,5.791 4.7909,4 6.9999,4 7.7319,4 8.4099,4.211 8.9999,4.555L8.9999,5.414 9.5859,6 7.9999,6 7.9999,10z M13.2279,4.813C12.0669,2.551 9.7169,1 6.9999,1 3.1339,1 -9.99999999997669E-05,4.134 -9.99999999997669E-05,8 -9.99999999997669E-05,11.866 3.1339,15 6.9999,15 9.7169,15 12.0669,13.449 13.2279,11.187L15.9999,8.414 15.9999,7.586z" />
                                                <GeometryDrawing  Brush="#FF414141" Geometry="F1M7,13C4.238,13 2,10.762 2,8 2,5.238 4.238,3 7,3 8.118,3 9.14,3.38 9.973,4L11.463,4C10.365,2.775 8.775,2 7,2 3.686,2 1,4.687 1,8 1,11.313 3.686,14 7,14 8.775,14 10.365,13.225 11.463,12L9.973,12C9.14,12.62,8.118,13,7,13" />
                                                <GeometryDrawing  Brush="#FF414141" Geometry="F1M12,5L10,5 12,7 9,7 9,9 12,9 10,11 12,11 15,8z" />
                                            </DrawingGroup.Children>
                                        </DrawingGroup>
                                    </DrawingBrush.Drawing>
                                </DrawingBrush>
                            </Rectangle.Fill>
                        </Rectangle>
                    </Viewbox>
                    <Label   Content="Salir"    />
                </StackPanel>
            </Button>

        </Grid>
        <!-- /Eof Grid con los tres botones [Ejecutar], [Cancelar],[ Terminar]  -->

    </Grid>

</Window>


↑↑↑

Código de la clase Value Objecto que mueve los datos

Esta clase se usa para mover informacion entre el subproceso Task y el formulario

El codigo de la misma esta en este enlace


↑↑↑

Código Visual Basic .Net


'   /** 
'   --------------------------------------------- 
'   [- (A) Contenido -] 
'   --------------------------------------------- 
'   ------------- 
'   [- Descripción   -] = 
'      Este formulario (WPF) es un estudio de como se puede usar un control [Barra de progreso (ProgressBar)] 
'      en un formulario WPF y que el control se actualice durante el proceso sin que la 
'      barra deje de funcionar  modificando el comportamiento básico. 
' 
'      En este ejemplo suponemos que representamos un contador de tiempo descendente, 
'      para ello usamos una [Barra de progreso (ProgressBar)] y un [Cuadro de texto (TextBox)] 
'      que indicara con un texto cuanto tiempo falta , y que para hacerlo mas bonito, 
'      he incluido dentro del control  (ProgressBar). 
'   ------------- 
'   [- Observaciones -] = 
'      Para evitar que el formulario se bloquee usamos la clase [System.Threading.Tasks.Task]. 
'      Para simular el paso del tiempo usamos un bucle For-Next que contiene el numero de segundos que queremos que dure el proceso. 
'      Para simular los segundos, dentro de cada iteración del bucle For-Next esperamos exactamente 1 segundo entre cada iteración 
'      y para ello usamos la instrucción [System.Threading.Thread.Sleep(1000)]. 
'      Para mover la información entre el subproceso [Task] y el formulario XAML, usamos una case [Value Object] llamada [DatosDevueltos]. 
'      Esta clase contiene dos propiedades de lectura:. 
'      a) El numero de segundos que falta para que se termine el proceso, (recibidos en el constructor). 
'      b) y el texto,  una cadena formateada con los minutos y segundos correspondientes al numero de segundos. 
'      Por ejemplo recibe 200 segundos y devuelve la cadena "03:20". 
'      Es decir 200 segundos son 3 minutos y 20 segundos. 
'      El valor numérico de los segundos se usa en la propiedad value del PorgressBar. 
'      La cadena se emplea en texto dentro del ProgressBar. 
'      El bucle que cuenta el tiempo, lo cuenta al revés, es decir quedan 50 segundos 49, 48, etc, 
'      de forma que la representación de los valores en los controles no es igual, 
'      la barra de progreso cuenta a delante, (han pasado un segundo, 2, 3, etc), 
'      y el texto muestra el tiempo que queda (quedan 20 segundos, 19, 18, etc). 
'      Lo que obliga a algún cambio de valores iniciales, en realidad no es ningún problema y se ve fácilmente si estas avisado. 
'      El código esta muy (excesivamente) documentado, para que cuando vuelva a necesitarlo, sepa lo que hace cada parte y por que. 
'   ------------- 
'   [- Bibliografía  -] = https://foxlearn.com/windows-forms/update-paramProgress-bar-from-async-task-in-csharp-352.html?utm_content = cmp-true. 
'   --------------------------------------------- 
'   [- /Eof -] 
'   */ 

'-------------------------------------------------------- 
' Necesario para la clase [CancellationTokenSource] 
Imports System.Threading 
' Necesario para la clase Task 
Imports System.Threading.Tasks 

Public Class WindowPruebaProgressBarConTask 

#Region 

    ''' <summary> 
    ''' El numero de segundos que estará activa la ventana 
    ''' </summary> 
    ''' <value> 
    ''' Un valor integer que representa el numero de segundos que se mostrara la ventana 
    ''' </value> 
    Public Property DuracionSegundos As Integer = 70I 

    ''' <summary> 
    ''' Tiempo que se espera en el bucle para simular un segundo 
    ''' El valor que tiene que tener esta contaste es de 1000 milisegundos 
    ''' (O sea 1 segundo) 
    '''  pongo un valor mas pequeño para pruebas 
    '''  Cambiar a 1000 para esperar un segundo entre iteraciones 
    ''' </summary> 
    Private Const MILISEGUNDOS_DE_ESPERA As Integer = 100 

#End Region 

#Region "Eventos Command de los botones [Aceptar], [Cancelar], [Terminar]" 

    Private localBotonEjecurarActivado As Boolean = True 
    Private localBotonCancelarActivado As Boolean = False 
    Private localBotonTerminarActivado As Boolean = True 

    Private Sub BotonEjecutarCommandBinding_Executed(sender As Object, e As ExecutedRoutedEventArgs) 
        '---------------------------------------------------- 
        'Arrancar la operación asincrónica. 
        Call ArrancarTareaAsicrona() 
    End Sub 

    Private Sub BotonEjecutarCommandBinding_CanExecute(sender As Object, e As CanExecuteRoutedEventArgs) 
        '  e.CanExecute = True 
        e.CanExecute = localBotonEjecurarActivado 
    End Sub 

    Private Sub BotonCancelarCommandBinding_Executed(sender As Object, e As ExecutedRoutedEventArgs) 
        '---------------------------------------------------- 
        'Cancelar la operación asincrónica. 
        Call CancelarTareaAsincrona() 
    End Sub 

    Private Sub BotonCancelarCommandBinding_CanExecute(sender As Object, e As CanExecuteRoutedEventArgs) 
        '  e.CanExecute = True 
        e.CanExecute = localBotonCancelarActivado 
    End Sub 

    Private Sub BotonCloseCommandBinding_Executed(sender As Object, e As ExecutedRoutedEventArgs) 
        '---------------------------------------------------- 
        'cerrar la ventana 
        '---------------------------------------------------- 
        Call DisposeTark() 
    End Sub 

    Private Sub BotonCloseCommandBinding_CanExecute(sender As Object, e As CanExecuteRoutedEventArgs) 
        '  e.CanExecute = True 
        e.CanExecute = localBotonTerminarActivado 
    End Sub 

#End Region 

#Region "Proceso Task" 

    ''' <summary> 
    ''' Para el informe de progreso. 
    ''' Proporciona un objeto IProgress(Of T) que invoca las devoluciones de llamada para cada valor de progreso notificado. 
    ''' </summary> 
    Private progress As New Progress(Of DatosDevueltos)(Sub(ByVal paramObjDatosVO As DatosDevueltos) 
                                                            pbStatus.Value = paramObjDatosVO.NumeroDeSegundos 
                                                            TextBoxCronometro.Text = paramObjDatosVO.TextoTiempoRestante 
                                                        End Sub) 

    ' Define el token de cancelación. 
    '  Señala un objeto CancellationToken que debe cancelarse 
    '  Para controlar la posible cancelación de la operación, 
    '  se crea una instancia de un objeto CancellationTokenSource 
    '  que genera un token de cancelación que se pasa a un objeto TaskFactory. 
    '  A su vez, el objeto TaskFactory pasa el token de cancelación 
    '  a cada una de las tareas que se ejecuten en segundo plano. 
    Private sourceCancellationToken As New CancellationTokenSource() 

    'Propaga la notificación de que las operaciones deberían cancelarse. 
    Private tokenCancellation As CancellationToken = sourceCancellationToken.Token 

    ''' <summary> 
    ''' Pone en marcha el proceso asíncrono 
    ''' </summary> 
    Private Async Sub ArrancarTareaAsicrona() 

        '---------------------------------------------------- 
        ' valores para el paramProgress bar que mueve este código 
        pbStatus.Maximum = DuracionSegundos 
        pbStatus.Minimum = 0 
        pbStatus.Value = 0 

        '-------------------------- 
        ' Situación de los botones 
        localBotonEjecurarActivado = False 
        localBotonCancelarActivado = True 
        localBotonTerminarActivado = False 

        '-------------------------- 
        ' Instanciar los objetos para que funcionen cada vez que se pulse [Ejecutar] 
        sourceCancellationToken = New CancellationTokenSource() 
        tokenCancellation = sourceCancellationToken.Token 

        '-------------------------- 
        'EjecutarTask se ejecuta en el grupo de subprocesos. 
        Await System.Threading.Tasks.Task.Run(Function() 
                                                  Return EjecutarTask(progress, tokenCancellation) 
                                              End Function) 

        'TextBoxCronometro.Text = "Terminado!" 

    End Sub 

    ''' <summary> 
    ''' El proceso que realiza el trabajo en el hilo 
    ''' En este caso cuenta segundos 
    ''' </summary> 
    ''' <param name = "paramProgress">Un objeto IProgress(Of T) que se usara para hacer notificaciones.</param> 
    ''' <param name = "paramTokenCancelacion">Propaga la notificación de que las operaciones deberían cancelarse.</param> 
    ''' <returns> 
    ''' Devuelve un valor lógico Integer (porque así lo he decidido) 
    ''' Return = 0 --> False, Ha habido algún error, el proceso no se ha completado 
    ''' Return = 1 --> True,  Todo correcto, el proceso se ha completado sin problemas 
    ''' </returns> 
    Private Function EjecutarTask(ByVal paramProgress As IProgress(Of DatosDevueltos), ByVal paramTokenCancelacion As CancellationToken) As Integer 

        '--------------------------- 
        ' En este programa (de prueba de concepto) se simula un contador de milisegundos 
        ' la constante [DuracionSegundos] contiene el numero de segundos que hay que esperar 
        ' Se actualiza la barra de progreso cada segundo 
        ' El bucle For-Next cuenta el número de segundos que se esperan 
        '--------------------------- 

        For i As Integer = DuracionSegundos To 0 Step -1 

            '------------------------------------- 
            ' la cancelación del proceso 
            If Not (paramTokenCancelacion = Nothing) Then 
                ' [IsCancellationRequested] Obtiene la información 
                ' sobre si se ha cancelado el proceso (True) o no (False) 
                If paramTokenCancelacion.IsCancellationRequested = True Then 
                    Return 0 ' False 
                    'Exit For 
                End If 
            End If 

            '------------------------------------- 
            ' simular una segundo de espera 
            ' un segundo = 1000 Milisegundos 
            Thread.Sleep(MILISEGUNDOS_DE_ESPERA) 

            '------------------------------------- 
            ' informe de progreso 
            If paramProgress IsNot Nothing Then 
                ' Cargar la información en la clase [DatosDevueltos] 
                ' y devolver esa clase a través del objeto [IProgress] 
                Dim objDatosVO As New DatosDevueltos(i) 
                paramProgress.Report(objDatosVO) 
            End If 
        Next 

        'valor que se devuelve 
        Return 1 ' true 
    End Function 

    Private Sub CancelarTareaAsincrona() 

        '-------------------------- 
        ' Situación de los botones 
        localBotonEjecurarActivado = True 
        localBotonCancelarActivado = False 
        localBotonTerminarActivado = True 

        sourceCancellationToken.Cancel() 
    End Sub 

    Public Sub DisposeTark() 
        '---------------------------------------------------- 
        'cerrar la ventana 
        '---------------------------------------------------- 
        ' Primero destruimos los objetos usados 
        If progress IsNot Nothing Then 
            progress = Nothing 
        End If 
        If sourceCancellationToken IsNot Nothing Then 
            sourceCancellationToken = Nothing 
        End If 
        If tokenCancellation <> Nothing Then 
            tokenCancellation = Nothing 
        End If 
        '---------------------------------------------------- 
        ' Después cerramos la ventana 
        Me.Close() 
    End Sub 
#End Region 

End Class