このドキュメントでは、アプリケーションでキャンセルを有効にする方法を示します。 この例では、Windows フォームを使用して、データフロー パイプラインで作業項目がアクティブである場所と、キャンセルの影響を示します。
注意
TPL データフロー ライブラリ ( System.Threading.Tasks.Dataflow 名前空間) は、.NET 6 以降のバージョンに含まれています。 .NET Framework および .NET Standard プロジェクトの場合は、 📦 System.Threading.Tasks.Dataflow NuGet パッケージをインストールする必要があります。
Windows フォーム アプリケーションを作成するには
C# または Visual Basic の Windows フォーム アプリケーション プロジェクトを作成します。 以降の手順では、プロジェクトの名前は
CancellationWinFormsとします。メイン フォーム Form1.cs (Visual Basic の Form1.vb) のフォーム デザイナーで、ToolStrip コントロールを追加します。
ToolStripButton コントロールに ToolStrip コントロールを追加します。 DisplayStyle プロパティを Text に設定し、Text プロパティを「作業項目の追加」に設定します。
ToolStripButton コントロールに 2 つ目の ToolStrip コントロールを追加します。 DisplayStyle プロパティを Text に、Text プロパティを「キャンセル」に、Enabled プロパティを
Falseに設定します。4 つの ToolStripProgressBar コントロールを ToolStrip コントロールに追加します。
データフロー パイプラインの作成
このセクションでは、作業項目を処理して進行状況バーを更新するデータフロー パイプラインを作成する方法を示します。
データフロー パイプラインを作成するには
プロジェクトで、System.Threading.Tasks.Dataflow.dll への参照を追加します。
Form1.cs (Visual Basic の Form1.vb) に次の
usingディレクティブ (Visual Basic ではImports) が含まれていることを確認します。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.DataflowWorkItemクラスの内部の型として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 ClassForm1クラスに次のデータ メンバーを追加します。// 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次の
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 フォーム アプリケーションでのデータフローの使用」を参照してください。
ユーザー インターフェイスへのデータフロー パイプラインの接続
このセクションでは、ユーザー インターフェイスにデータフロー パイプラインを接続する方法を説明します。 パイプラインの作成とパイプラインへの作業項目の追加は、どちらも [作業項目の追加] ボタンのインベント ハンドラーによって制御されます。 キャンセルは [キャンセル] ボタンによって開始されます。 ユーザーがこのいずれかのボタンをクリックすると、適切な操作が非同期的に開始されます。
ユーザー インターフェイスにデータフロー パイプラインを接続するには
メイン フォームのフォーム デザイナーで、Click ボタンの イベントのイベント ハンドラーを作成します。
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メイン フォームのフォーム デザイナーで、Click ボタンの イベント ハンドラーのイベント ハンドラーを作成します。
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 (Visual Basic の Form1.vb) のコード全体を示しています。
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
次の図は、実行中のアプリケーションを示しています。
関連項目
.NET