Tutorial: Uso de flujos de datos en aplicaciones de Windows FormsWalkthrough: Using Dataflow in a Windows Forms Application

Este documento muestra cómo crear una red de bloques de flujo de datos que realizan el procesamiento de imágenes en una aplicación de Windows Forms.This document demonstrates how to create a network of dataflow blocks that perform image processing in a Windows Forms application.

En este ejemplo se cargan archivos de imagen de la carpeta especificada, se crea una imagen compuesta y se muestra el resultado.This example loads image files from the specified folder, creates a composite image, and displays the result. En el ejemplo se utiliza el modelo de flujo de datos para distribuir las imágenes por la red.The example uses the dataflow model to route images through the network. En el modelo de flujo de datos, los componentes independientes de un programa se comunican entre sí mediante mensajes.In the dataflow model, independent components of a program communicate with one another by sending messages. Cuando un componente recibe un mensaje, realiza alguna acción y pasa el resultado a otro componente.When a component receives a message, it performs some action and then passes the result to another component. Compare esto con el modelo de flujo de control, en el que una aplicación utiliza estructuras de control, como por ejemplo, instrucciones condicionales, bucles, etc., para controlar el orden de las operaciones en un programa.Compare this with the control flow model, in which an application uses control structures, for example, conditional statements, loops, and so on, to control the order of operations in a program.

Requisitos previosPrerequisites

Lea Flujo de datos antes de empezar este tutorial.Read Dataflow before you start this walkthrough.

Nota

La biblioteca de flujos de datos TPL (el espacio de nombres System.Threading.Tasks.Dataflow) no se distribuye con .NET.The TPL Dataflow Library (the System.Threading.Tasks.Dataflow namespace) is not distributed with .NET. Para instalar el espacio de nombres System.Threading.Tasks.Dataflow en Visual Studio, abra el proyecto, seleccione Administrar paquetes NuGet en el menú Proyecto y busque en línea el paquete System.Threading.Tasks.Dataflow.To install the System.Threading.Tasks.Dataflow namespace in Visual Studio, open your project, choose Manage NuGet Packages from the Project menu, and search online for the System.Threading.Tasks.Dataflow package. Como alternativa, para realizar la instalación con la CLI de .Net Core, ejecute dotnet add package System.Threading.Tasks.Dataflow.Alternatively, to install it using the .Net Core CLI, run dotnet add package System.Threading.Tasks.Dataflow.

SeccionesSections

Este tutorial contiene las siguientes secciones:This walkthrough contains the following sections:

Crear una aplicación de Windows FormsCreating the Windows Forms Application

En esta sección se describe cómo crear la aplicación básica de Windows Forms y agregar controles al formulario principal.This section describes how to create the basic Windows Forms application and add controls to the main form.

Para crear la aplicación de Windows FormsTo Create the Windows Forms Application

  1. En Visual Studio, cree un proyecto Aplicación de Windows Forms de Visual C# o Visual Basic.In Visual Studio, create a Visual C# or Visual Basic Windows Forms Application project. En este documento, el proyecto se denomina CompositeImages.In this document, the project is named CompositeImages.

  2. En el diseñador de formularios del formulario principal, Form1.cs (Form1.vb para Visual Basic), agregue un control ToolStrip.On the form designer for the main form, Form1.cs (Form1.vb for Visual Basic), add a ToolStrip control.

  3. Agregue un control ToolStripButton al control ToolStrip.Add a ToolStripButton control to the ToolStrip control. Establezca la propiedad DisplayStyle en Text y la propiedad Text en Elegir carpeta.Set the DisplayStyle property to Text and the Text property to Choose Folder.

  4. Agregue un segundo control ToolStripButton al control ToolStrip.Add a second ToolStripButton control to the ToolStrip control. Establezca la propiedad DisplayStyle en Text, la propiedad Text en Cancelar y la propiedad Enabled en False.Set the DisplayStyle property to Text, the Text property to Cancel, and the Enabled property to False.

  5. Agregue un objeto PictureBox al formulario principal.Add a PictureBox object to the main form. Establezca la propiedad Dock en Fill.Set the Dock property to Fill.

Crear la red del flujo de datosCreating the Dataflow Network

En esta sección se describe cómo crear la red del flujo de datos que lleva a cabo el procesamiento de imágenes.This section describes how to create the dataflow network that performs image processing.

Para crear la red del flujo de datosTo Create the Dataflow Network

  1. Agregue a su proyecto una referencia a System.Threading.Tasks.Dataflow.dll.Add a reference to System.Threading.Tasks.Dataflow.dll to your project.

  2. Asegúrese de que Form1.cs (Form1.vb para Visual Basic) contenga las siguientes instrucciones using (Using en Visual Basic):Ensure that Form1.cs (Form1.vb for Visual Basic) contains the following using (Using in Visual Basic) statements:

    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;
    
  3. Agregue a la clase Form1 los miembros de datos siguientes:Add the following data members to the Form1 class:

    // The head of the dataflow network.
    ITargetBlock<string> headBlock = null;
    
    // Enables the user interface to signal cancellation to the network.
    CancellationTokenSource cancellationTokenSource;
    
  4. Agregue el método siguiente, CreateImageProcessingNetwork, a la clase Form1.Add the following method, CreateImageProcessingNetwork, to the Form1 class. Este método crea la red de procesamiento de imágenes.This method creates the image processing network.

    // 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;
    }
    
  5. Implemente el método LoadBitmaps.Implement the LoadBitmaps method.

    // 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;
    }
    
  6. Implemente el método CreateCompositeBitmap.Implement the CreateCompositeBitmap method.

    // 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;
    }
    

    Nota

    La versión de C# del método CreateCompositeBitmap utiliza punteros para permitir un procesamiento eficaz de los objetos System.Drawing.Bitmap.The C# version of the CreateCompositeBitmap method uses pointers to enable efficient processing of the System.Drawing.Bitmap objects. Por lo tanto, debe habilitar la opción Permitir código no seguro en el proyecto para utilizar la palabra clave unsafe.Therefore, you must enable the Allow unsafe code option in your project in order to use the unsafe keyword. Para obtener más información sobre cómo habilitar el código no seguro en un proyecto de Visual C#, vea Compilar (Página, Diseñador de proyectos) (C#).For more information about how to enable unsafe code in a Visual C# project, see Build Page, Project Designer (C#).

En la tabla siguiente se describen los miembros de la red.The following table describes the members of the network.

MiembroMember TipoType DESCRIPCIÓNDescription
loadBitmaps TransformBlock<TInput,TOutput> Acepta una ruta de carpeta como entrada y genera una colección de objetos Bitmap como salida.Takes a folder path as input and produces a collection of Bitmap objects as output.
createCompositeBitmap TransformBlock<TInput,TOutput> Acepta una colección de objetos Bitmap como entrada y produce un mapa de bits compuesto como salida.Takes a collection of Bitmap objects as input and produces a composite bitmap as output.
displayCompositeBitmap ActionBlock<TInput> Muestra el mapa de bits compuesto en el formulario.Displays the composite bitmap on the form.
operationCancelled ActionBlock<TInput> Muestra una imagen para indicar que la operación se cancela y permite al usuario seleccionar otra carpeta.Displays an image to indicate that the operation is canceled and enables the user to select another folder.

Para conectar los bloques de flujo de datos para formar una red, este ejemplo usa el método LinkTo.To connect the dataflow blocks to form a network, this example uses the LinkTo method. El método LinkTo contiene una versión sobrecargada que adopta un objeto Predicate<T> que determina si el bloque de destino acepta o rechaza un mensaje.The LinkTo method contains an overloaded version that takes a Predicate<T> object that determines whether the target block accepts or rejects a message. Este mecanismo de filtrado permite que los bloques de mensajes reciban solo ciertos valores.This filtering mechanism enables message blocks to receive only certain values. En este ejemplo, la red puede dividirse en ramas de una de dos maneras.In this example, the network can branch in one of two ways. La rama principal carga las imágenes desde el disco, crea la imagen compuesta y la muestra en el formulario.The main branch loads the images from disk, creates the composite image, and displays that image on the form. La rama alternativa cancela la operación actual.The alternate branch cancels the current operation. Los objetos Predicate<T> permiten que los bloques de flujo de datos en la rama principal cambien a la rama alternativa al rechazar determinados mensajes.The Predicate<T> objects enable the dataflow blocks along the main branch to switch to the alternative branch by rejecting certain messages. Por ejemplo, si el usuario cancela la operación, el bloque de flujo de datos createCompositeBitmap produce null (Nothing en Visual Basic) como salida.For example, if the user cancels the operation, the dataflow block createCompositeBitmap produces null (Nothing in Visual Basic) as its output. El bloque de flujo de datos displayCompositeBitmap rechaza valores de entrada null y, por lo tanto, el mensaje se ofrece a operationCancelled.The dataflow block displayCompositeBitmap rejects null input values, and therefore, the message is offered to operationCancelled. El bloque de flujo de datos operationCancelled acepta todos los mensajes y, por lo tanto, muestra una imagen para indicar que se ha cancelado la operación.The dataflow block operationCancelled accepts all messages and therefore, displays an image to indicate that the operation is canceled.

En la ilustración siguiente se muestra la red de procesamiento de imágenes:The following illustration shows the image processing network:

Ilustración en la que se muestra la red de procesamiento de imágenes.

Dado que los bloques de flujo de datos displayCompositeBitmap y operationCancelled actúan sobre la interfaz de usuario, es importante que esta acción se produzca en el subproceso de interfaz de usuario.Because the displayCompositeBitmap and operationCancelled dataflow blocks act on the user interface, it is important that these actions occur on the user-interface thread. Para lograrlo, durante la construcción, cada uno de estos objetos proporciona un objeto ExecutionDataflowBlockOptions que tiene la propiedad TaskScheduler establecida como TaskScheduler.FromCurrentSynchronizationContext.To accomplish this, during construction, these objects each provide a ExecutionDataflowBlockOptions object that has the TaskScheduler property set to TaskScheduler.FromCurrentSynchronizationContext. El método TaskScheduler.FromCurrentSynchronizationContext crea un objeto TaskScheduler que funciona en el contexto de sincronización actual.The TaskScheduler.FromCurrentSynchronizationContext method creates a TaskScheduler object that performs work on the current synchronization context. Como se llama al método CreateImageProcessingNetwork desde el controlador del botón Elegir carpeta, que se ejecuta en el subproceso de la interfaz de usuario, las acciones para los bloques de flujo de datos displayCompositeBitmap y operationCancelled también se ejecutan en el subproceso de la interfaz de usuario.Because the CreateImageProcessingNetwork method is called from the handler of the Choose Folder button, which runs on the user-interface thread, the actions for the displayCompositeBitmap and operationCancelled dataflow blocks also run on the user-interface thread.

Este ejemplo usa un token de cancelación compartido en lugar de establecer la propiedad CancellationToken, porque la propiedad CancellationToken cancela permanentemente la ejecución del bloque de flujo de datos.This example uses a shared cancellation token instead of setting the CancellationToken property because the CancellationToken property permanently cancels dataflow block execution. En este ejemplo, un token de cancelación permite reutilizar la misma red del flujo de datos varias veces, incluso cuando el usuario cancela una o varias operaciones.A cancellation token enables this example to reuse the same dataflow network multiple times, even when the user cancels one or more operations. Para obtener un ejemplo que usa CancellationToken para cancelar la ejecución de un bloque de flujo de datos de modo permanente, vea Cómo: Cómo: Cancelar un bloque de flujos de datos.For an example that uses CancellationToken to permanently cancel the execution of a dataflow block, see How to: Cancel a Dataflow Block.

Conectar la red del flujo de datos a la interfaz de usuarioConnecting the Dataflow Network to the User Interface

En esta sección se describe cómo conectar la red del flujo de datos a la interfaz de usuario.This section describes how to connect the dataflow network to the user interface. La creación de la imagen compuesta y la cancelación de la operación se inician desde los botones Elegir carpeta y Cancelar.The creation of the composite image and cancellation of the operation are initiated from the Choose Folder and Cancel buttons. Cuando el usuario elige cualquiera de ellos, se inicia la acción correspondiente de forma asincrónica.When the user chooses either of these buttons, the appropriate action is initiated in an asynchronous manner.

Para conectar la red del flujo de datos a la interfaz de usuarioTo Connect the Dataflow Network to the User Interface

  1. En el diseñador de formularios del formulario principal, cree un controlador de eventos para el evento Click del botón Elegir carpeta.On the form designer for the main form, create an event handler for the Click event for the Choose Folder button.

  2. Implemente el evento Click del botón Elegir carpeta.Implement the Click event for the Choose Folder button.

    // 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.
          if (headBlock == null)
          {
             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;
       }         
    }
    
  3. En el diseñador de formularios del formulario principal, cree un controlador de eventos para el evento Click del botón Cancelar.On the form designer for the main form, create an event handler for the Click event for the Cancel button.

  4. Implemente el evento Click del botón Cancelar.Implement the Click event for the Cancel button.

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

Ejemplo completoThe Complete Example

En el ejemplo siguiente se muestra el código completo de este tutorial.The following example shows the complete code for this walkthrough.

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.
            if (headBlock == null)
            {
               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();
      }
   }
}

En la ilustración siguiente se muestra la salida típica de la carpeta \Sample Pictures\ común.The following illustration shows typical output for the common \Sample Pictures\ folder.

Aplicación de Windows FormsThe Windows Forms Application

Vea tambiénSee also