Поделиться через


Практическое руководство. Отмена блока потока данных

В этом документе показано, как включить отмену в приложении. В этом примере используется Windows Forms для отображения позиции активности рабочих элементов в конвейере потока данных, а также последствий отмены.

Примечание.

Библиотека потоков данных TPL (пространство имен System.Threading.Tasks.Dataflow) не поставляется с .NET. Чтобы установить пространство имен System.Threading.Tasks.Dataflow в Visual Studio, откройте проект, выберите Управление пакетами NuGet в меню Проект и выполните поиск пакета System.Threading.Tasks.Dataflow в Интернете. Вы также можете установить его, выполнив в .NET Core CLI команду dotnet add package System.Threading.Tasks.Dataflow.

Создание приложения Windows Forms

  1. Создайте проект Приложение Windows Forms на C# или Visual Basic. На следующих этапах проекту дается название CancellationWinForms.

  2. В конструкторе форм главной формы Form1.cs (Form1.vb для Visual Basic) добавьте элемент управления ToolStrip.

  3. Добавьте элемент управления ToolStripButton к элементу управления ToolStrip. Задайте свойству DisplayStyle значение Text, а свойству TextДобавить рабочие элементы.

  4. Добавьте второй элемент управления ToolStripButton к элементу управления ToolStrip. Задайте свойству DisplayStyleзначение Text, свойству Text значение Отмена, а свойству Enabled — значение False.

  5. Добавьте четыре объекта ToolStripProgressBar в элемент управления ToolStrip.

Создание конвейера потока данных

В этом разделе описано, как создать конвейер потока данных, который обрабатывает рабочие элементы и обновляет индикаторы выполнения.

Создание конвейера потока данных

  1. В проекте добавьте ссылку на System.Threading.Tasks.Dataflow.dll.

  2. Убедитесь, что Form1.cs (Form1.vb для Visual Basic) содержит следующие using директивы (Imports в Visual Basic).

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Threading.Tasks.Dataflow;
    using System.Windows.Forms;
    
    Imports System.Threading
    Imports System.Threading.Tasks
    Imports System.Threading.Tasks.Dataflow
    
    
  3. Добавьте класс WorkItem как внутренний тип класса Form1.

    // A placeholder type that performs work.
    class WorkItem
    {
       // Performs work for the provided number of milliseconds.
       public void DoWork(int milliseconds)
       {
          // For demonstration, suspend the current thread.
          Thread.Sleep(milliseconds);
       }
    }
    
    ' A placeholder type that performs work.
    Private Class WorkItem
        ' Performs work for the provided number of milliseconds.
        Public Sub DoWork(ByVal milliseconds As Integer)
            ' For demonstration, suspend the current thread.
            Thread.Sleep(milliseconds)
        End Sub
    End Class
    
  4. Добавьте в класс Form1 следующие данные-члены.

    // Enables the user interface to signal cancellation.
    CancellationTokenSource cancellationSource;
    
    // The first node in the dataflow pipeline.
    TransformBlock<WorkItem, WorkItem> startWork;
    
    // The second, and final, node in the dataflow pipeline.
    ActionBlock<WorkItem> completeWork;
    
    // Increments the value of the provided progress bar.
    ActionBlock<ToolStripProgressBar> incrementProgress;
    
    // Decrements the value of the provided progress bar.
    ActionBlock<ToolStripProgressBar> decrementProgress;
    
    // Enables progress bar actions to run on the UI thread.
    TaskScheduler uiTaskScheduler;
    
    ' Enables the user interface to signal cancellation.
    Private cancellationSource As CancellationTokenSource
    
    ' The first node in the dataflow pipeline.
    Private startWork As TransformBlock(Of WorkItem, WorkItem)
    
    ' The second, and final, node in the dataflow pipeline.
    Private completeWork As ActionBlock(Of WorkItem)
    
    ' Increments the value of the provided progress bar.
    Private incrementProgress As ActionBlock(Of ToolStripProgressBar)
    
    ' Decrements the value of the provided progress bar.
    Private decrementProgress As ActionBlock(Of ToolStripProgressBar)
    
    ' Enables progress bar actions to run on the UI thread.
    Private uiTaskScheduler As TaskScheduler
    
  5. Добавьте в класс CreatePipeline метод Form1.

    // Creates the blocks that participate in the dataflow pipeline.
    private void CreatePipeline()
    {
       // Create the cancellation source.
       cancellationSource = new CancellationTokenSource();
    
       // Create the first node in the pipeline.
       startWork = new TransformBlock<WorkItem, WorkItem>(workItem =>
       {
          // Perform some work.
          workItem.DoWork(250);
    
          // Decrement the progress bar that tracks the count of
          // active work items in this stage of the pipeline.
          decrementProgress.Post(toolStripProgressBar1);
    
          // Increment the progress bar that tracks the count of
          // active work items in the next stage of the pipeline.
          incrementProgress.Post(toolStripProgressBar2);
    
          // Send the work item to the next stage of the pipeline.
          return workItem;
       },
       new ExecutionDataflowBlockOptions
       {
          CancellationToken = cancellationSource.Token
       });
    
       // Create the second, and final, node in the pipeline.
       completeWork = new ActionBlock<WorkItem>(workItem =>
       {
          // Perform some work.
          workItem.DoWork(1000);
    
          // Decrement the progress bar that tracks the count of
          // active work items in this stage of the pipeline.
          decrementProgress.Post(toolStripProgressBar2);
    
          // Increment the progress bar that tracks the overall
          // count of completed work items.
          incrementProgress.Post(toolStripProgressBar3);
       },
       new ExecutionDataflowBlockOptions
       {
          CancellationToken = cancellationSource.Token,
          MaxDegreeOfParallelism = 2
       });
    
       // Connect the two nodes of the pipeline. When the first node completes,
       // set the second node also to the completed state.
       startWork.LinkTo(
          completeWork, new DataflowLinkOptions { PropagateCompletion = true });
    
       // Create the dataflow action blocks that increment and decrement
       // progress bars.
       // These blocks use the task scheduler that is associated with
       // the UI thread.
    
       incrementProgress = new ActionBlock<ToolStripProgressBar>(
          progressBar => progressBar.Value++,
          new ExecutionDataflowBlockOptions
          {
             CancellationToken = cancellationSource.Token,
             TaskScheduler = uiTaskScheduler
          });
    
       decrementProgress = new ActionBlock<ToolStripProgressBar>(
          progressBar => progressBar.Value--,
          new ExecutionDataflowBlockOptions
          {
             CancellationToken = cancellationSource.Token,
             TaskScheduler = uiTaskScheduler
          });
    }
    
    ' Creates the blocks that participate in the dataflow pipeline.
    Private Sub CreatePipeline()
        ' Create the cancellation source.
        cancellationSource = New CancellationTokenSource()
    
        ' Create the first node in the pipeline.
        startWork = New TransformBlock(Of WorkItem, WorkItem)(Function(workItem)
                                                                  ' Perform some work.
                                                                  ' Decrement the progress bar that tracks the count of
                                                                  ' active work items in this stage of the pipeline.
                                                                  ' Increment the progress bar that tracks the count of
                                                                  ' active work items in the next stage of the pipeline.
                                                                  ' Send the work item to the next stage of the pipeline.
                                                                  workItem.DoWork(250)
                                                                  decrementProgress.Post(toolStripProgressBar1)
                                                                  incrementProgress.Post(toolStripProgressBar2)
                                                                  Return workItem
                                                              End Function,
        New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token})
    
        ' Create the second, and final, node in the pipeline.
        completeWork = New ActionBlock(Of WorkItem)(Sub(workItem)
                                                        ' Perform some work.
                                                        ' Decrement the progress bar that tracks the count of
                                                        ' active work items in this stage of the pipeline.
                                                        ' Increment the progress bar that tracks the overall
                                                        ' count of completed work items.
                                                        workItem.DoWork(1000)
                                                        decrementProgress.Post(toolStripProgressBar2)
                                                        incrementProgress.Post(toolStripProgressBar3)
                                                    End Sub,
        New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token,
                                                .MaxDegreeOfParallelism = 2})
    
        ' Connect the two nodes of the pipeline. When the first node completes,
        ' set the second node also to the completed state.
        startWork.LinkTo(
           completeWork, New DataflowLinkOptions With {.PropagateCompletion = true})
    
        ' Create the dataflow action blocks that increment and decrement
        ' progress bars.
        ' These blocks use the task scheduler that is associated with
        ' the UI thread.
    
        incrementProgress = New ActionBlock(Of ToolStripProgressBar)(
           Sub(progressBar) progressBar.Value += 1,
           New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token,
                                                   .TaskScheduler = uiTaskScheduler})
    
        decrementProgress = New ActionBlock(Of ToolStripProgressBar)(
           Sub(progressBar) progressBar.Value -= 1,
           New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token,
                                                   .TaskScheduler = uiTaskScheduler})
    
    End Sub
    

Поскольку блоки потоков данных incrementProgress и decrementProgress работают с интерфейсом пользователя, важно, чтобы эти действия происходили в потоке пользовательского интерфейса. Для этого во время строительства эти объекты предоставляют ExecutionDataflowBlockOptions объект, которому TaskScheduler присвоено свойство TaskScheduler.FromCurrentSynchronizationContext. Метод TaskScheduler.FromCurrentSynchronizationContext создает объект TaskScheduler, выполняющий работу в текущем контексте синхронизации. Поскольку конструктор Form1 вызывается из потока пользовательского интерфейса, действия для блоков потоков данных incrementProgress и decrementProgress также выполняются в потоке пользовательского интерфейса.

В этом примере задается свойство CancellationToken при создании частей конвейера. Поскольку свойство CancellationToken полностью отменяет выполнение блока потока данных, весь конвейер должен быть заново создан после того, как пользователь отменит операцию и затем захочет добавить нескольких рабочих элементов в конвейер. Пример, демонстрирующий альтернативный способ отмены блока потока данных, чтобы другие работы могли выполняться после отмены операции, см. в разделе Пошаговое руководство. Использование потока данных в приложении Windows Forms.

Подключение конвейера потока данных к пользовательскому интерфейсу

В этом разделе описывается, как подключить конвейер потока данных к интерфейсу пользователя. Как созданием конвейера, так и добавлением рабочих элементов в конвейере управляет обработчик событий для кнопки Добавление рабочих элементов. Отмена инициируется кнопкой Отмена. Когда пользователь нажимает какую-либо из этих кнопок, соответствующее действие выполняется асинхронным образом.

Подключение конвейера потока данных к пользовательскому интерфейсу

  1. В конструкторе форм главной формы создайте обработчик событий для события Click кнопки Добавить рабочие элементы.

  2. Реализуйте событие Click для кнопки Добавить рабочие элементы.

    // Event handler for the Add Work Items button.
    private void toolStripButton1_Click(object sender, EventArgs e)
    {
       // The Cancel button is disabled when the pipeline is not active.
       // Therefore, create the pipeline and enable the Cancel button
       // if the Cancel button is disabled.
       if (!toolStripButton2.Enabled)
       {
          CreatePipeline();
    
          // Enable the Cancel button.
          toolStripButton2.Enabled = true;
       }
    
       // Post several work items to the head of the pipeline.
       for (int i = 0; i < 5; i++)
       {
          toolStripProgressBar1.Value++;
          startWork.Post(new WorkItem());
       }
    }
    
    ' Event handler for the Add Work Items button.
    Private Sub toolStripButton1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles toolStripButton1.Click
        ' The Cancel button is disabled when the pipeline is not active.
        ' Therefore, create the pipeline and enable the Cancel button
        ' if the Cancel button is disabled.
        If Not toolStripButton2.Enabled Then
            CreatePipeline()
    
            ' Enable the Cancel button.
            toolStripButton2.Enabled = True
        End If
    
        ' Post several work items to the head of the pipeline.
        For i As Integer = 0 To 4
            toolStripProgressBar1.Value += 1
            startWork.Post(New WorkItem())
        Next i
    End Sub
    
  3. В конструкторе форм главной формы создайте обработчик событий для события Click кнопки Отмена.

  4. Реализуйте обработчик события Click для кнопки Отмена.

    // Event handler for the Cancel button.
    private async void toolStripButton2_Click(object sender, EventArgs e)
    {
       // Disable both buttons.
       toolStripButton1.Enabled = false;
       toolStripButton2.Enabled = false;
    
       // Trigger cancellation.
       cancellationSource.Cancel();
    
       try
       {
          // Asynchronously wait for the pipeline to complete processing and for
          // the progress bars to update.
          await Task.WhenAll(
             completeWork.Completion,
             incrementProgress.Completion,
             decrementProgress.Completion);
       }
       catch (OperationCanceledException)
       {
       }
    
       // Increment the progress bar that tracks the number of cancelled
       // work items by the number of active work items.
       toolStripProgressBar4.Value += toolStripProgressBar1.Value;
       toolStripProgressBar4.Value += toolStripProgressBar2.Value;
    
       // Reset the progress bars that track the number of active work items.
       toolStripProgressBar1.Value = 0;
       toolStripProgressBar2.Value = 0;
    
       // Enable the Add Work Items button.
       toolStripButton1.Enabled = true;
    }
    
    ' Event handler for the Cancel button.
    Private Async Sub toolStripButton2_Click(ByVal sender As Object, ByVal e As EventArgs) Handles toolStripButton2.Click
        ' Disable both buttons.
        toolStripButton1.Enabled = False
        toolStripButton2.Enabled = False
    
        ' Trigger cancellation.
        cancellationSource.Cancel()
    
        Try
            ' Asynchronously wait for the pipeline to complete processing and for
            ' the progress bars to update.
            Await Task.WhenAll(completeWork.Completion, incrementProgress.Completion, decrementProgress.Completion)
        Catch e1 As OperationCanceledException
        End Try
    
        ' Increment the progress bar that tracks the number of cancelled
        ' work items by the number of active work items.
        toolStripProgressBar4.Value += toolStripProgressBar1.Value
        toolStripProgressBar4.Value += toolStripProgressBar2.Value
    
        ' Reset the progress bars that track the number of active work items.
        toolStripProgressBar1.Value = 0
        toolStripProgressBar2.Value = 0
    
        ' Enable the Add Work Items button.
        toolStripButton1.Enabled = True
    End Sub
    

Пример

В следующем примере приведен полный код Form1.cs (Form1.vb для Visual Basic).

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;

namespace CancellationWinForms
{
   public partial class Form1 : Form
   {
      // A placeholder type that performs work.
      class WorkItem
      {
         // Performs work for the provided number of milliseconds.
         public void DoWork(int milliseconds)
         {
            // For demonstration, suspend the current thread.
            Thread.Sleep(milliseconds);
         }
      }

      // Enables the user interface to signal cancellation.
      CancellationTokenSource cancellationSource;

      // The first node in the dataflow pipeline.
      TransformBlock<WorkItem, WorkItem> startWork;

      // The second, and final, node in the dataflow pipeline.
      ActionBlock<WorkItem> completeWork;

      // Increments the value of the provided progress bar.
      ActionBlock<ToolStripProgressBar> incrementProgress;

      // Decrements the value of the provided progress bar.
      ActionBlock<ToolStripProgressBar> decrementProgress;

      // Enables progress bar actions to run on the UI thread.
      TaskScheduler uiTaskScheduler;

      public Form1()
      {
         InitializeComponent();

         // Create the UI task scheduler from the current synchronization
         // context.
         uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
      }

      // Creates the blocks that participate in the dataflow pipeline.
      private void CreatePipeline()
      {
         // Create the cancellation source.
         cancellationSource = new CancellationTokenSource();

         // Create the first node in the pipeline.
         startWork = new TransformBlock<WorkItem, WorkItem>(workItem =>
         {
            // Perform some work.
            workItem.DoWork(250);

            // Decrement the progress bar that tracks the count of
            // active work items in this stage of the pipeline.
            decrementProgress.Post(toolStripProgressBar1);

            // Increment the progress bar that tracks the count of
            // active work items in the next stage of the pipeline.
            incrementProgress.Post(toolStripProgressBar2);

            // Send the work item to the next stage of the pipeline.
            return workItem;
         },
         new ExecutionDataflowBlockOptions
         {
            CancellationToken = cancellationSource.Token
         });

         // Create the second, and final, node in the pipeline.
         completeWork = new ActionBlock<WorkItem>(workItem =>
         {
            // Perform some work.
            workItem.DoWork(1000);

            // Decrement the progress bar that tracks the count of
            // active work items in this stage of the pipeline.
            decrementProgress.Post(toolStripProgressBar2);

            // Increment the progress bar that tracks the overall
            // count of completed work items.
            incrementProgress.Post(toolStripProgressBar3);
         },
         new ExecutionDataflowBlockOptions
         {
            CancellationToken = cancellationSource.Token,
            MaxDegreeOfParallelism = 2
         });

         // Connect the two nodes of the pipeline. When the first node completes,
         // set the second node also to the completed state.
         startWork.LinkTo(
            completeWork, new DataflowLinkOptions { PropagateCompletion = true });

         // Create the dataflow action blocks that increment and decrement
         // progress bars.
         // These blocks use the task scheduler that is associated with
         // the UI thread.

         incrementProgress = new ActionBlock<ToolStripProgressBar>(
            progressBar => progressBar.Value++,
            new ExecutionDataflowBlockOptions
            {
               CancellationToken = cancellationSource.Token,
               TaskScheduler = uiTaskScheduler
            });

         decrementProgress = new ActionBlock<ToolStripProgressBar>(
            progressBar => progressBar.Value--,
            new ExecutionDataflowBlockOptions
            {
               CancellationToken = cancellationSource.Token,
               TaskScheduler = uiTaskScheduler
            });
      }

      // Event handler for the Add Work Items button.
      private void toolStripButton1_Click(object sender, EventArgs e)
      {
         // The Cancel button is disabled when the pipeline is not active.
         // Therefore, create the pipeline and enable the Cancel button
         // if the Cancel button is disabled.
         if (!toolStripButton2.Enabled)
         {
            CreatePipeline();

            // Enable the Cancel button.
            toolStripButton2.Enabled = true;
         }

         // Post several work items to the head of the pipeline.
         for (int i = 0; i < 5; i++)
         {
            toolStripProgressBar1.Value++;
            startWork.Post(new WorkItem());
         }
      }

      // Event handler for the Cancel button.
      private async void toolStripButton2_Click(object sender, EventArgs e)
      {
         // Disable both buttons.
         toolStripButton1.Enabled = false;
         toolStripButton2.Enabled = false;

         // Trigger cancellation.
         cancellationSource.Cancel();

         try
         {
            // Asynchronously wait for the pipeline to complete processing and for
            // the progress bars to update.
            await Task.WhenAll(
               completeWork.Completion,
               incrementProgress.Completion,
               decrementProgress.Completion);
         }
         catch (OperationCanceledException)
         {
         }

         // Increment the progress bar that tracks the number of cancelled
         // work items by the number of active work items.
         toolStripProgressBar4.Value += toolStripProgressBar1.Value;
         toolStripProgressBar4.Value += toolStripProgressBar2.Value;

         // Reset the progress bars that track the number of active work items.
         toolStripProgressBar1.Value = 0;
         toolStripProgressBar2.Value = 0;

         // Enable the Add Work Items button.
         toolStripButton1.Enabled = true;
      }

      ~Form1()
      {
         cancellationSource.Dispose();
      }
   }
}
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Threading.Tasks.Dataflow


Namespace CancellationWinForms
    Partial Public Class Form1
        Inherits Form
        ' A placeholder type that performs work.
        Private Class WorkItem
            ' Performs work for the provided number of milliseconds.
            Public Sub DoWork(ByVal milliseconds As Integer)
                ' For demonstration, suspend the current thread.
                Thread.Sleep(milliseconds)
            End Sub
        End Class

        ' Enables the user interface to signal cancellation.
        Private cancellationSource As CancellationTokenSource

        ' The first node in the dataflow pipeline.
        Private startWork As TransformBlock(Of WorkItem, WorkItem)

        ' The second, and final, node in the dataflow pipeline.
        Private completeWork As ActionBlock(Of WorkItem)

        ' Increments the value of the provided progress bar.
        Private incrementProgress As ActionBlock(Of ToolStripProgressBar)

        ' Decrements the value of the provided progress bar.
        Private decrementProgress As ActionBlock(Of ToolStripProgressBar)

        ' Enables progress bar actions to run on the UI thread.
        Private uiTaskScheduler As TaskScheduler

        Public Sub New()
            InitializeComponent()

            ' Create the UI task scheduler from the current synchronization
            ' context.
            uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
        End Sub

        ' Creates the blocks that participate in the dataflow pipeline.
        Private Sub CreatePipeline()
            ' Create the cancellation source.
            cancellationSource = New CancellationTokenSource()

            ' Create the first node in the pipeline.
            startWork = New TransformBlock(Of WorkItem, WorkItem)(Function(workItem)
                                                                      ' Perform some work.
                                                                      ' Decrement the progress bar that tracks the count of
                                                                      ' active work items in this stage of the pipeline.
                                                                      ' Increment the progress bar that tracks the count of
                                                                      ' active work items in the next stage of the pipeline.
                                                                      ' Send the work item to the next stage of the pipeline.
                                                                      workItem.DoWork(250)
                                                                      decrementProgress.Post(toolStripProgressBar1)
                                                                      incrementProgress.Post(toolStripProgressBar2)
                                                                      Return workItem
                                                                  End Function,
            New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token})

            ' Create the second, and final, node in the pipeline.
            completeWork = New ActionBlock(Of WorkItem)(Sub(workItem)
                                                            ' Perform some work.
                                                            ' Decrement the progress bar that tracks the count of
                                                            ' active work items in this stage of the pipeline.
                                                            ' Increment the progress bar that tracks the overall
                                                            ' count of completed work items.
                                                            workItem.DoWork(1000)
                                                            decrementProgress.Post(toolStripProgressBar2)
                                                            incrementProgress.Post(toolStripProgressBar3)
                                                        End Sub,
            New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token,
                                                    .MaxDegreeOfParallelism = 2})

            ' Connect the two nodes of the pipeline. When the first node completes,
            ' set the second node also to the completed state.
            startWork.LinkTo(
               completeWork, New DataflowLinkOptions With {.PropagateCompletion = true})

            ' Create the dataflow action blocks that increment and decrement
            ' progress bars.
            ' These blocks use the task scheduler that is associated with
            ' the UI thread.

            incrementProgress = New ActionBlock(Of ToolStripProgressBar)(
               Sub(progressBar) progressBar.Value += 1,
               New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token,
                                                       .TaskScheduler = uiTaskScheduler})

            decrementProgress = New ActionBlock(Of ToolStripProgressBar)(
               Sub(progressBar) progressBar.Value -= 1,
               New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token,
                                                       .TaskScheduler = uiTaskScheduler})

        End Sub

        ' Event handler for the Add Work Items button.
        Private Sub toolStripButton1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles toolStripButton1.Click
            ' The Cancel button is disabled when the pipeline is not active.
            ' Therefore, create the pipeline and enable the Cancel button
            ' if the Cancel button is disabled.
            If Not toolStripButton2.Enabled Then
                CreatePipeline()

                ' Enable the Cancel button.
                toolStripButton2.Enabled = True
            End If

            ' Post several work items to the head of the pipeline.
            For i As Integer = 0 To 4
                toolStripProgressBar1.Value += 1
                startWork.Post(New WorkItem())
            Next i
        End Sub

        ' Event handler for the Cancel button.
        Private Async Sub toolStripButton2_Click(ByVal sender As Object, ByVal e As EventArgs) Handles toolStripButton2.Click
            ' Disable both buttons.
            toolStripButton1.Enabled = False
            toolStripButton2.Enabled = False

            ' Trigger cancellation.
            cancellationSource.Cancel()

            Try
                ' Asynchronously wait for the pipeline to complete processing and for
                ' the progress bars to update.
                Await Task.WhenAll(completeWork.Completion, incrementProgress.Completion, decrementProgress.Completion)
            Catch e1 As OperationCanceledException
            End Try

            ' Increment the progress bar that tracks the number of cancelled
            ' work items by the number of active work items.
            toolStripProgressBar4.Value += toolStripProgressBar1.Value
            toolStripProgressBar4.Value += toolStripProgressBar2.Value

            ' Reset the progress bars that track the number of active work items.
            toolStripProgressBar1.Value = 0
            toolStripProgressBar2.Value = 0

            ' Enable the Add Work Items button.
            toolStripButton1.Enabled = True
        End Sub

        Protected Overrides Sub Finalize()
            cancellationSource.Dispose()
            MyBase.Finalize()
        End Sub
    End Class
End Namespace

Ниже показано запущенное приложение.

Приложение Windows Forms

См. также