この記事では、Windows フォーム アプリケーションで画像処理を実行するデータフロー ブロックのネットワークを作成する方法について説明します。
次の使用例は、指定したフォルダーからイメージ ファイルを読み込み、複合イメージを作成して結果を表示します。 この例では、データフロー モデルを使用して、ネットワーク経由でイメージをルーティングします。 このデータフロー モデルでは、プログラム内の独立したコンポーネント同士が、メッセージを送信することによって相互に通信します。 コンポーネントは、メッセージを受信すると、何らかのアクションを実行し、結果を別のコンポーネントに渡します。 これを制御フロー モデルと比較します。このモデルでは、アプリケーションが制御構造 (条件ステートメント、ループなど) を使用してプログラム内の操作の順序を制御します。
[前提条件]
このチュートリアルを開始する前に 、データフロー を読み取ってください。
注
TPL データフロー ライブラリ ( System.Threading.Tasks.Dataflow 名前空間) は、.NET 6 以降のバージョンに含まれています。 .NET Framework および .NET Standard プロジェクトの場合は、 📦 System.Threading.Tasks.Dataflow NuGet パッケージをインストールする必要があります。
セクション
このチュートリアルは、次のセクションで構成されています。
Windows フォーム アプリケーションの作成
このセクションでは、基本的な Windows フォーム アプリケーションを作成し、メイン フォームにコントロールを追加する方法について説明します。
Windows フォーム アプリケーションを作成するには
Visual Studio で、Visual C# または Visual Basic Windows フォーム アプリケーション プロジェクトを作成します 。 このドキュメントでは、プロジェクトの名前は
CompositeImagesです。メイン フォームのフォーム デザイナーで、Form1.cs (Visual Basic の場合はForm1.vb)、 ToolStrip コントロールを追加します。
ToolStripButton コントロールにToolStrip コントロールを追加します。 DisplayStyleプロパティをTextに設定し、Textプロパティを [フォルダーの選択] に設定します。
ToolStripButton コントロールに 2 つ目のToolStrip コントロールを追加します。 DisplayStyle プロパティを Text、Text プロパティを Cancel に設定し、Enabled プロパティを
Falseに設定します。メイン フォームに PictureBox オブジェクトを追加します。 Dock プロパティを Fillに設定します。
データフロー ネットワークの作成
このセクションでは、画像処理を実行するデータフロー ネットワークを作成する方法について説明します。
データフロー ネットワークを作成するには
System.Threading.Tasks.Dataflow.dll への参照をプロジェクトに追加します。
Form1.cs (Visual Basic のForm1.vb) に次の
using(Visual Basic のUsing) ステートメントが含まれていることを確認します。using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; using System.Windows.Forms;Form1クラスに次のデータ メンバーを追加します。// The head of the dataflow network. ITargetBlock<string> headBlock = null; // Enables the user interface to signal cancellation to the network. CancellationTokenSource cancellationTokenSource;CreateImageProcessingNetworkクラスに次のメソッド (Form1) を追加します。 このメソッドは、画像処理ネットワークを作成します。// Creates the image processing dataflow network and returns the // head node of the network. ITargetBlock<string> CreateImageProcessingNetwork() { // // Create the dataflow blocks that form the network. // // Create a dataflow block that takes a folder path as input // and returns a collection of Bitmap objects. var loadBitmaps = new TransformBlock<string, IEnumerable<Bitmap>>(path => { try { return LoadBitmaps(path); } catch (OperationCanceledException) { // Handle cancellation by passing the empty collection // to the next stage of the network. return Enumerable.Empty<Bitmap>(); } }); // Create a dataflow block that takes a collection of Bitmap objects // and returns a single composite bitmap. var createCompositeBitmap = new TransformBlock<IEnumerable<Bitmap>, Bitmap>(bitmaps => { try { return CreateCompositeBitmap(bitmaps); } catch (OperationCanceledException) { // Handle cancellation by passing null to the next stage // of the network. return null; } }); // Create a dataflow block that displays the provided bitmap on the form. var displayCompositeBitmap = new ActionBlock<Bitmap>(bitmap => { // Display the bitmap. pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage; pictureBox1.Image = bitmap; // Enable the user to select another folder. toolStripButton1.Enabled = true; toolStripButton2.Enabled = false; Cursor = DefaultCursor; }, // Specify a task scheduler from the current synchronization context // so that the action runs on the UI thread. new ExecutionDataflowBlockOptions { TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext() }); // Create a dataflow block that responds to a cancellation request by // displaying an image to indicate that the operation is cancelled and // enables the user to select another folder. var operationCancelled = new ActionBlock<object>(delegate { // Display the error image to indicate that the operation // was cancelled. pictureBox1.SizeMode = PictureBoxSizeMode.CenterImage; pictureBox1.Image = pictureBox1.ErrorImage; // Enable the user to select another folder. toolStripButton1.Enabled = true; toolStripButton2.Enabled = false; Cursor = DefaultCursor; }, // Specify a task scheduler from the current synchronization context // so that the action runs on the UI thread. new ExecutionDataflowBlockOptions { TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext() }); // // Connect the network. // // Link loadBitmaps to createCompositeBitmap. // The provided predicate ensures that createCompositeBitmap accepts the // collection of bitmaps only if that collection has at least one member. loadBitmaps.LinkTo(createCompositeBitmap, bitmaps => bitmaps.Count() > 0); // Also link loadBitmaps to operationCancelled. // When createCompositeBitmap rejects the message, loadBitmaps // offers the message to operationCancelled. // operationCancelled accepts all messages because we do not provide a // predicate. loadBitmaps.LinkTo(operationCancelled); // Link createCompositeBitmap to displayCompositeBitmap. // The provided predicate ensures that displayCompositeBitmap accepts the // bitmap only if it is non-null. createCompositeBitmap.LinkTo(displayCompositeBitmap, bitmap => bitmap != null); // Also link createCompositeBitmap to operationCancelled. // When displayCompositeBitmap rejects the message, createCompositeBitmap // offers the message to operationCancelled. // operationCancelled accepts all messages because we do not provide a // predicate. createCompositeBitmap.LinkTo(operationCancelled); // Return the head of the network. return loadBitmaps; }LoadBitmapsメソッドを実装します。// Loads all bitmap files that exist at the provided path. IEnumerable<Bitmap> LoadBitmaps(string path) { List<Bitmap> bitmaps = new List<Bitmap>(); // Load a variety of image types. foreach (string bitmapType in new string[] { "*.bmp", "*.gif", "*.jpg", "*.png", "*.tif" }) { // Load each bitmap for the current extension. foreach (string fileName in Directory.GetFiles(path, bitmapType)) { // Throw OperationCanceledException if cancellation is requested. cancellationTokenSource.Token.ThrowIfCancellationRequested(); try { // Add the Bitmap object to the collection. bitmaps.Add(new Bitmap(fileName)); } catch (Exception) { // TODO: A complete application might handle the error. } } } return bitmaps; }CreateCompositeBitmapメソッドを実装します。// Creates a composite bitmap from the provided collection of Bitmap objects. // This method computes the average color of each pixel among all bitmaps // to create the composite image. Bitmap CreateCompositeBitmap(IEnumerable<Bitmap> bitmaps) { Bitmap[] bitmapArray = bitmaps.ToArray(); // Compute the maximum width and height components of all // bitmaps in the collection. Rectangle largest = new Rectangle(); foreach (var bitmap in bitmapArray) { if (bitmap.Width > largest.Width) largest.Width = bitmap.Width; if (bitmap.Height > largest.Height) largest.Height = bitmap.Height; } // Create a 32-bit Bitmap object with the greatest dimensions. Bitmap result = new Bitmap(largest.Width, largest.Height, PixelFormat.Format32bppArgb); // Lock the result Bitmap. var resultBitmapData = result.LockBits( new Rectangle(new Point(), result.Size), ImageLockMode.WriteOnly, result.PixelFormat); // Lock each source bitmap to create a parallel list of BitmapData objects. var bitmapDataList = (from bitmap in bitmapArray select bitmap.LockBits( new Rectangle(new Point(), bitmap.Size), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb)) .ToList(); // Compute each column in parallel. Parallel.For(0, largest.Width, new ParallelOptions { CancellationToken = cancellationTokenSource.Token }, i => { // Compute each row. for (int j = 0; j < largest.Height; j++) { // Counts the number of bitmaps whose dimensions // contain the current location. int count = 0; // The sum of all alpha, red, green, and blue components. int a = 0, r = 0, g = 0, b = 0; // For each bitmap, compute the sum of all color components. foreach (var bitmapData in bitmapDataList) { // Ensure that we stay within the bounds of the image. if (bitmapData.Width > i && bitmapData.Height > j) { unsafe { byte* row = (byte*)(bitmapData.Scan0 + (j * bitmapData.Stride)); byte* pix = (byte*)(row + (4 * i)); a += *pix; pix++; r += *pix; pix++; g += *pix; pix++; b += *pix; } count++; } } //prevent divide by zero in bottom right pixelless corner if (count == 0) break; unsafe { // Compute the average of each color component. a /= count; r /= count; g /= count; b /= count; // Set the result pixel. byte* row = (byte*)(resultBitmapData.Scan0 + (j * resultBitmapData.Stride)); byte* pix = (byte*)(row + (4 * i)); *pix = (byte)a; pix++; *pix = (byte)r; pix++; *pix = (byte)g; pix++; *pix = (byte)b; } } }); // Unlock the source bitmaps. for (int i = 0; i < bitmapArray.Length; i++) { bitmapArray[i].UnlockBits(bitmapDataList[i]); } // Unlock the result bitmap. result.UnlockBits(resultBitmapData); // Return the result. return result; }注
CreateCompositeBitmapメソッドの C# バージョンでは、ポインターを使用して、System.Drawing.Bitmap オブジェクトを効率的に処理できます。 したがって、unsafe キーワードを使用するには、プロジェクトで [安全でないコードを許可する] オプションを有効にする必要があります。 Visual C# プロジェクトで安全でないコードを有効にする方法の詳細については、「 ビルド ページ、プロジェクト デザイナー (C#)」を参照してください。
ネットワークのメンバーを次の表に示します。
| メンバー | タイプ | Description |
|---|---|---|
loadBitmaps |
TransformBlock<TInput,TOutput> | フォルダー パスを入力として受け取り、 Bitmap オブジェクトのコレクションを出力として生成します。 |
createCompositeBitmap |
TransformBlock<TInput,TOutput> | Bitmap オブジェクトのコレクションを入力として受け取り、出力として複合ビットマップを生成します。 |
displayCompositeBitmap |
ActionBlock<TInput> | フォーム上に複合ビットマップを表示します。 |
operationCancelled |
ActionBlock<TInput> | 操作が取り消されたことを示す画像を表示し、ユーザーが別のフォルダーを選択できるようにします。 |
この例では、データフロー ブロックを接続してネットワークを形成するために、 LinkTo メソッドを使用します。
LinkTo メソッドには、ターゲット ブロックがメッセージを受け入れるか拒否するかを決定するPredicate<T> オブジェクトを受け取るオーバーロードされたバージョンが含まれています。 このフィルター処理メカニズムにより、メッセージ ブロックは特定の値のみを受信できます。 この例では、ネットワークは 2 つの方法のいずれかで分岐できます。 メイン ブランチは、ディスクからイメージを読み込み、複合イメージを作成して、そのイメージをフォームに表示します。 代替分岐は、現在の操作を取り消します。
Predicate<T> オブジェクトを使用すると、メイン ブランチに沿ったデータフロー ブロックで、特定のメッセージを拒否して代替ブランチに切り替えることができます。 たとえば、ユーザーが操作を取り消した場合、データフロー ブロック createCompositeBitmap は出力として null (Visual Basic でNothing ) を生成します。 データフロー ブロックdisplayCompositeBitmapnull入力値が拒否されるため、メッセージはoperationCancelledに提供されます。 データフロー ブロック operationCancelled はすべてのメッセージを受け入れるため、操作が取り消されたことを示す画像が表示されます。
次の図は、イメージ処理ネットワークを示しています。
displayCompositeBitmapおよびoperationCancelledデータフロー ブロックはユーザー インターフェイスで動作するため、これらのアクションはユーザー インターフェイス スレッドで発生することが重要です。 これを実現するために、構築時に、これらのオブジェクトはそれぞれ、ExecutionDataflowBlockOptions プロパティが TaskScheduler に設定されているTaskScheduler.FromCurrentSynchronizationContext オブジェクトを提供します。
TaskScheduler.FromCurrentSynchronizationContext メソッドは、現在の同期コンテキストで作業を実行するTaskScheduler オブジェクトを作成します。
CreateImageProcessingNetwork メソッドは、ユーザー インターフェイス スレッドで実行される [フォルダーの選択] ボタンのハンドラーから呼び出されるため、displayCompositeBitmapおよびoperationCancelledデータフロー ブロックのアクションもユーザー インターフェイス スレッドで実行されます。
この例では、CancellationToken プロパティがデータフロー ブロックの実行を完全に取り消すので、CancellationToken プロパティを設定する代わりに、共有キャンセル トークンを使用します。 キャンセル トークンを使用すると、ユーザーが 1 つ以上の操作をキャンセルした場合でも、この例では同じデータフロー ネットワークを複数回再利用できます。 CancellationTokenを使用してデータフロー ブロックの実行を完全に取り消す例については、「方法: データフロー ブロックをキャンセルする」を参照してください。
ユーザー インターフェイスへのデータフロー ネットワークの接続
このセクションでは、データフロー ネットワークをユーザー インターフェイスに接続する方法について説明します。 複合イメージの作成と操作の取り消しは、[ フォルダーの選択] ボタンと [キャンセル] ボタンから開始されます。 ユーザーがこれらのボタンのいずれかを選択すると、適切なアクションが非同期的に開始されます。
データフロー ネットワークをユーザー インターフェイスに接続するには
メイン フォームのフォーム デザイナーで、[Click] ボタンの イベントのイベント ハンドラーを作成します。
[Click] ボタンの イベントを実装します。
// Event handler for the Choose Folder button. private void toolStripButton1_Click(object sender, EventArgs e) { // Create a FolderBrowserDialog object to enable the user to // select a folder. FolderBrowserDialog dlg = new FolderBrowserDialog { ShowNewFolderButton = false }; // Set the selected path to the common Sample Pictures folder // if it exists. string initialDirectory = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.CommonPictures), "Sample Pictures"); if (Directory.Exists(initialDirectory)) { dlg.SelectedPath = initialDirectory; } // Show the dialog and process the dataflow network. if (dlg.ShowDialog() == DialogResult.OK) { // Create a new CancellationTokenSource object to enable // cancellation. cancellationTokenSource = new CancellationTokenSource(); // Create the image processing network if needed. headBlock ??= CreateImageProcessingNetwork(); // Post the selected path to the network. headBlock.Post(dlg.SelectedPath); // Enable the Cancel button and disable the Choose Folder button. toolStripButton1.Enabled = false; toolStripButton2.Enabled = true; // Show a wait cursor. Cursor = Cursors.WaitCursor; } }メイン フォームのフォーム デザイナーで、[Click] ボタンの イベントのイベント ハンドラーを作成します。
Click] ボタンの イベントを実装します。
// Event handler for the Cancel button. private void toolStripButton2_Click(object sender, EventArgs e) { // Signal the request for cancellation. The current component of // the dataflow network will respond to the cancellation request. cancellationTokenSource.Cancel(); }
完全な例
次の例は、このチュートリアルの完全なコードを示しています。
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;
namespace CompositeImages
{
public partial class Form1 : Form
{
// The head of the dataflow network.
ITargetBlock<string> headBlock = null;
// Enables the user interface to signal cancellation to the network.
CancellationTokenSource cancellationTokenSource;
public Form1()
{
InitializeComponent();
}
// Creates the image processing dataflow network and returns the
// head node of the network.
ITargetBlock<string> CreateImageProcessingNetwork()
{
//
// Create the dataflow blocks that form the network.
//
// Create a dataflow block that takes a folder path as input
// and returns a collection of Bitmap objects.
var loadBitmaps = new TransformBlock<string, IEnumerable<Bitmap>>(path =>
{
try
{
return LoadBitmaps(path);
}
catch (OperationCanceledException)
{
// Handle cancellation by passing the empty collection
// to the next stage of the network.
return Enumerable.Empty<Bitmap>();
}
});
// Create a dataflow block that takes a collection of Bitmap objects
// and returns a single composite bitmap.
var createCompositeBitmap = new TransformBlock<IEnumerable<Bitmap>, Bitmap>(bitmaps =>
{
try
{
return CreateCompositeBitmap(bitmaps);
}
catch (OperationCanceledException)
{
// Handle cancellation by passing null to the next stage
// of the network.
return null;
}
});
// Create a dataflow block that displays the provided bitmap on the form.
var displayCompositeBitmap = new ActionBlock<Bitmap>(bitmap =>
{
// Display the bitmap.
pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
pictureBox1.Image = bitmap;
// Enable the user to select another folder.
toolStripButton1.Enabled = true;
toolStripButton2.Enabled = false;
Cursor = DefaultCursor;
},
// Specify a task scheduler from the current synchronization context
// so that the action runs on the UI thread.
new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
});
// Create a dataflow block that responds to a cancellation request by
// displaying an image to indicate that the operation is cancelled and
// enables the user to select another folder.
var operationCancelled = new ActionBlock<object>(delegate
{
// Display the error image to indicate that the operation
// was cancelled.
pictureBox1.SizeMode = PictureBoxSizeMode.CenterImage;
pictureBox1.Image = pictureBox1.ErrorImage;
// Enable the user to select another folder.
toolStripButton1.Enabled = true;
toolStripButton2.Enabled = false;
Cursor = DefaultCursor;
},
// Specify a task scheduler from the current synchronization context
// so that the action runs on the UI thread.
new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
});
//
// Connect the network.
//
// Link loadBitmaps to createCompositeBitmap.
// The provided predicate ensures that createCompositeBitmap accepts the
// collection of bitmaps only if that collection has at least one member.
loadBitmaps.LinkTo(createCompositeBitmap, bitmaps => bitmaps.Count() > 0);
// Also link loadBitmaps to operationCancelled.
// When createCompositeBitmap rejects the message, loadBitmaps
// offers the message to operationCancelled.
// operationCancelled accepts all messages because we do not provide a
// predicate.
loadBitmaps.LinkTo(operationCancelled);
// Link createCompositeBitmap to displayCompositeBitmap.
// The provided predicate ensures that displayCompositeBitmap accepts the
// bitmap only if it is non-null.
createCompositeBitmap.LinkTo(displayCompositeBitmap, bitmap => bitmap != null);
// Also link createCompositeBitmap to operationCancelled.
// When displayCompositeBitmap rejects the message, createCompositeBitmap
// offers the message to operationCancelled.
// operationCancelled accepts all messages because we do not provide a
// predicate.
createCompositeBitmap.LinkTo(operationCancelled);
// Return the head of the network.
return loadBitmaps;
}
// Loads all bitmap files that exist at the provided path.
IEnumerable<Bitmap> LoadBitmaps(string path)
{
List<Bitmap> bitmaps = new List<Bitmap>();
// Load a variety of image types.
foreach (string bitmapType in
new string[] { "*.bmp", "*.gif", "*.jpg", "*.png", "*.tif" })
{
// Load each bitmap for the current extension.
foreach (string fileName in Directory.GetFiles(path, bitmapType))
{
// Throw OperationCanceledException if cancellation is requested.
cancellationTokenSource.Token.ThrowIfCancellationRequested();
try
{
// Add the Bitmap object to the collection.
bitmaps.Add(new Bitmap(fileName));
}
catch (Exception)
{
// TODO: A complete application might handle the error.
}
}
}
return bitmaps;
}
// Creates a composite bitmap from the provided collection of Bitmap objects.
// This method computes the average color of each pixel among all bitmaps
// to create the composite image.
Bitmap CreateCompositeBitmap(IEnumerable<Bitmap> bitmaps)
{
Bitmap[] bitmapArray = bitmaps.ToArray();
// Compute the maximum width and height components of all
// bitmaps in the collection.
Rectangle largest = new Rectangle();
foreach (var bitmap in bitmapArray)
{
if (bitmap.Width > largest.Width)
largest.Width = bitmap.Width;
if (bitmap.Height > largest.Height)
largest.Height = bitmap.Height;
}
// Create a 32-bit Bitmap object with the greatest dimensions.
Bitmap result = new Bitmap(largest.Width, largest.Height,
PixelFormat.Format32bppArgb);
// Lock the result Bitmap.
var resultBitmapData = result.LockBits(
new Rectangle(new Point(), result.Size), ImageLockMode.WriteOnly,
result.PixelFormat);
// Lock each source bitmap to create a parallel list of BitmapData objects.
var bitmapDataList = (from bitmap in bitmapArray
select bitmap.LockBits(
new Rectangle(new Point(), bitmap.Size),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb))
.ToList();
// Compute each column in parallel.
Parallel.For(0, largest.Width, new ParallelOptions
{
CancellationToken = cancellationTokenSource.Token
},
i =>
{
// Compute each row.
for (int j = 0; j < largest.Height; j++)
{
// Counts the number of bitmaps whose dimensions
// contain the current location.
int count = 0;
// The sum of all alpha, red, green, and blue components.
int a = 0, r = 0, g = 0, b = 0;
// For each bitmap, compute the sum of all color components.
foreach (var bitmapData in bitmapDataList)
{
// Ensure that we stay within the bounds of the image.
if (bitmapData.Width > i && bitmapData.Height > j)
{
unsafe
{
byte* row = (byte*)(bitmapData.Scan0 + (j * bitmapData.Stride));
byte* pix = (byte*)(row + (4 * i));
a += *pix; pix++;
r += *pix; pix++;
g += *pix; pix++;
b += *pix;
}
count++;
}
}
//prevent divide by zero in bottom right pixelless corner
if (count == 0)
break;
unsafe
{
// Compute the average of each color component.
a /= count;
r /= count;
g /= count;
b /= count;
// Set the result pixel.
byte* row = (byte*)(resultBitmapData.Scan0 + (j * resultBitmapData.Stride));
byte* pix = (byte*)(row + (4 * i));
*pix = (byte)a; pix++;
*pix = (byte)r; pix++;
*pix = (byte)g; pix++;
*pix = (byte)b;
}
}
});
// Unlock the source bitmaps.
for (int i = 0; i < bitmapArray.Length; i++)
{
bitmapArray[i].UnlockBits(bitmapDataList[i]);
}
// Unlock the result bitmap.
result.UnlockBits(resultBitmapData);
// Return the result.
return result;
}
// Event handler for the Choose Folder button.
private void toolStripButton1_Click(object sender, EventArgs e)
{
// Create a FolderBrowserDialog object to enable the user to
// select a folder.
FolderBrowserDialog dlg = new FolderBrowserDialog
{
ShowNewFolderButton = false
};
// Set the selected path to the common Sample Pictures folder
// if it exists.
string initialDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonPictures),
"Sample Pictures");
if (Directory.Exists(initialDirectory))
{
dlg.SelectedPath = initialDirectory;
}
// Show the dialog and process the dataflow network.
if (dlg.ShowDialog() == DialogResult.OK)
{
// Create a new CancellationTokenSource object to enable
// cancellation.
cancellationTokenSource = new CancellationTokenSource();
// Create the image processing network if needed.
headBlock ??= CreateImageProcessingNetwork();
// Post the selected path to the network.
headBlock.Post(dlg.SelectedPath);
// Enable the Cancel button and disable the Choose Folder button.
toolStripButton1.Enabled = false;
toolStripButton2.Enabled = true;
// Show a wait cursor.
Cursor = Cursors.WaitCursor;
}
}
// Event handler for the Cancel button.
private void toolStripButton2_Click(object sender, EventArgs e)
{
// Signal the request for cancellation. The current component of
// the dataflow network will respond to the cancellation request.
cancellationTokenSource.Cancel();
}
~Form1()
{
cancellationTokenSource.Dispose();
}
}
}
次の図は、一般的な \Sample Pictures\ フォルダーの一般的な出力を示しています。
こちらも参照ください
.NET