Iniziare a creare soluzioni con la libreria di client Batch per .NET

Questo articolo consente di apprendere le nozioni di base di Azure Batch e della libreria di Batch .NET esaminando nel dettaglio un'applicazione C# di esempio. Viene illustrato come questa applicazione di esempio usa il servizio Batch per elaborare un carico di lavoro parallelo nel cloud e come interagisce con Archiviazione di Azure per la gestione temporanea e il recupero di file. Saranno disponibili informazioni su un flusso di lavoro comune dell'applicazione Batch e sui principali componenti di Batch, ad esempio processi, attività, pool e nodi di calcolo.

Flusso di lavoro della soluzione Batch (di base)

Prerequisiti

Questo articolo presuppone che si sia in grado di usare C# e Visual Studio e di soddisfare i requisiti di creazione dell'account specificati di seguito per Azure e per i servizi Batch e di archiviazione.

Account

Importante

Batch supporta attualmente solo account di archiviazione per utilizzo generico, come descritto nel passaggio 5 Creare un account di archiviazione dell'articolo Informazioni sugli account di archiviazione di Azure.

Visual Studio

Per compilare il progetto di esempio, è necessario Visual Studio 2015 o versioni successive. Le versioni gratuite e di valutazione di Visual Studio sono disponibili nella panoramica dei prodotti Visual Studio.

DotNetTutorial

L'esempio DotNetTutorial è uno dei molti esempi di codice Batch disponibili nel repository azure-batch-samples in GitHub. È possibile scaricare tutti gli esempi facendo clic su Clone or download > Download ZIP (Clona o scarica > Scarica ZIP) nella home page del repository oppure facendo clic sul collegamento di download diretto azure-batch-samples-master.zip. Dopo l'estrazione dei contenuti del file ZIP, la soluzione sarà disponibile nella cartella seguente:

\azure-batch-samples\CSharp\ArticleProjects\DotNetTutorial

Azure Batch Explorer (facoltativo)

Azure Batch Explorer è un'utilità gratuita inclusa nel repository azure-batch-samples in GitHub. Nonostante non sia necessaria per completare questa esercitazione, può essere utile durante lo sviluppo e il debug delle soluzioni Batch.

Panoramica del progetto di esempio DotNetTutorial

L'esempio di codice DotNetTutorial è una soluzione di Visual Studio costituita da due progetti: DotNetTutorial e TaskApplication.

  • DotNetTutorial è l'applicazione client che interagisce con i servizi Batch e Archiviazione per eseguire un carico di lavoro parallelo nei nodi di calcolo (macchine virtuali). L'esempio DotNetTutorial viene eseguito nella workstation locale.
  • TaskApplication è il programma che viene eseguito nei nodi di calcolo in Azure per completare le operazioni effettive. Nell'esempio, TaskApplication.exe analizza il testo in un file scaricato da Archiviazione di Azure (file di input). Produce quindi un file di testo (file di output) che contiene un elenco delle prime tre parole visualizzate nel file di input. Dopo la creazione del file di output, TaskApplication carica il file in Archiviazione di Azure, rendendolo disponibile all'applicazione client per il download. TaskApplication viene eseguito in parallelo su più nodi di calcolo nel servizio Batch.

Il diagramma seguente illustra le operazioni principali eseguite dall'applicazione client, DotNetTutorial, e dall'applicazione eseguita dalle attività, TaskApplication. Questo flusso di lavoro di base è tipico di molte soluzioni di calcolo create con Batch. Anche se non illustra ogni funzionalità disponibile nel servizio Batch, quasi tutti gli scenari di Batch includono parti di questo flusso di lavoro.

Flusso di lavoro dell'esempio di Batch

Passaggio 1. Creare contenitori nell'archivio BLOB di Azure.
Passaggio 2. Caricare i file dell'applicazione dell'attività e i file di input nei contenitori.
Passaggio 3. Creare un pool di Batch.
    3a. L'attività StartTask del pool scarica i file binari delle attività (TaskApplication) nei nodi quando questi vengono aggiunti al pool.
Passaggio 4. Creare un processo di Batch.
Passaggio 5. Aggiungere attività al processo.
    5a. Viene pianificata l'esecuzione delle attività nei nodi.
    5b. Ogni attività scarica i rispettivi dati di input da Archiviazione di Azure e quindi avvia l'esecuzione.
Passaggio 6. Monitorare le attività.
    6a. Dopo il completamento, le attività caricano i rispettivi dati di output in Archiviazione di Azure.
Passaggio 7. Scaricare l'output delle attività dal servizio di archiviazione.

Come indicato, non tutte le soluzioni Batch eseguiranno esattamente questi passaggi e potrebbero includerne molti altri, ma l'applicazione di esempio DotNetTutorial illustra i processi comuni presenti in una soluzione Batch.

Compilare il progetto di esempio DotNetTutorial

Per eseguire correttamente l'esempio è prima necessario specificare le credenziali dell'account Batch e dell'account di archiviazione nel file Program.cs del progetto DotNetTutorial. Se non è già stato fatto, aprire la soluzione in Visual Studio facendo doppio clic sul file della soluzione DotNetTutorial.sln . In alternativa, aprirlo in Visual Studio dal menu File > Apri > Progetto/Soluzione.

Aprire Program.cs nel progetto DotNetTutorial . Aggiungere quindi le proprie credenziali, come specificato nella parte iniziale del file:

// Update the Batch and Storage account credential strings below with the values
// unique to your accounts. These are used when constructing connection strings
// for the Batch and Storage client objects.

// Batch account credentials
private const string BatchAccountName = "";
private const string BatchAccountKey  = "";
private const string BatchAccountUrl  = "";

// Storage account credentials
private const string StorageAccountName = "";
private const string StorageAccountKey  = "";

Importante

Come indicato sopra, attualmente è necessario specificare le credenziali per un account di archiviazione per utilizzo generico in Archiviazione di Azure. Le applicazioni Batch usano l'archivio BLOB nell'account di archiviazione per utilizzo generico . Non specificare credenziali per un account di archiviazione creato selezionando il tipo di account Archiviazione BLOB .

Le credenziali dell'account Batch e dell'account di archiviazione sono disponibili nel pannello dell'account di ogni servizio nel portale di Azure:

Credenziali di Batch nel portale Credenziali di archiviazione nel portale

Dopo avere aggiornato il progetto con le proprie credenziali, fare clic con il pulsante destro del mouse sulla soluzione in Esplora soluzioni e scegliere Compila soluzione. Confermare il ripristino di eventuali pacchetti NuGet, se richiesto.

Suggerimento

Se i pacchetti NuGet non vengono ripristinati automaticamente o se vengono visualizzati errori relativi all'impossibilità di ripristinare i pacchetti, assicurarsi che Gestione pacchetti NuGet sia installato, quindi abilitare il download dei pacchetti mancanti. Per abilitare il download dei pacchetti, vedere Enabling Package Restore During Build (Abilitazione del ripristino dei pacchetti durante la compilazione).

Nelle sezioni seguenti si esamineranno in dettaglio i passaggi eseguiti dall'applicazione di esempio per l'elaborazione di un carico di lavoro nel servizio Batch. È consigliabile fare riferimento alla soluzione aperta in Visual Studio mentre si esamina il resto di questo articolo, perché non vengono illustrate tutte le righe di codice dell'esempio.

Passare all'inizio del metodo MainAsync nel file Program.cs del progetto DotNetTutorial per iniziare con il passaggio 1. Ogni passaggio riportato segue quindi approssimativamente la successione di chiamate ai metodi in MainAsync.

Passaggio 1: Creare contenitori di archiviazione

Creare contenitori in Archiviazione di Azure

Batch include il supporto predefinito per l'interazione con Archiviazione di Azure. I contenitori nell'account di archiviazione forniranno i file necessari per le attività eseguite nell'account Batch, oltre a una posizione in cui archiviare i dati di output prodotti. La prima operazione eseguita dall'applicazione client DotNetTutorial è la creazione di tre contenitori nell'archivio BLOB di Azure:

  • application: in questo contenitore verranno archiviate l'applicazione eseguita dalle attività e le eventuali dipendenze, ad esempio le DLL.
  • input: le attività scaricheranno i file di dati da elaborare dal contenitore input .
  • output: dopo aver completato l'elaborazione dei file di input, le attività caricheranno i risultati nel contenitore output .

Per interagire con un account di archiviazione e creare contenitori, viene usata la libreria client di archiviazione di Azure per .NET. Viene creato un riferimento all'account con CloudStorageAccount, da cui viene quindi creato un oggetto CloudBlobClient:

// Construct the Storage account connection string
string storageConnectionString = String.Format(
    "DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1}",
    StorageAccountName,
    StorageAccountKey);

// Retrieve the storage account
CloudStorageAccount storageAccount =
    CloudStorageAccount.Parse(storageConnectionString);

// Create the blob client, for use in obtaining references to
// blob storage containers
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();

Il riferimento blobClient viene usato in tutta l'applicazione e viene passato come parametro a diversi metodi. Un esempio è costituito dal blocco di codice immediatamente successivo, in cui viene chiamato CreateContainerIfNotExistAsync per creare effettivamente i contenitori.

// Use the blob client to create the containers in Azure Storage if they don't
// yet exist
const string appContainerName    = "application";
const string inputContainerName  = "input";
const string outputContainerName = "output";
await CreateContainerIfNotExistAsync(blobClient, appContainerName);
await CreateContainerIfNotExistAsync(blobClient, inputContainerName);
await CreateContainerIfNotExistAsync(blobClient, outputContainerName);
private static async Task CreateContainerIfNotExistAsync(
    CloudBlobClient blobClient,
    string containerName)
{
        CloudBlobContainer container =
            blobClient.GetContainerReference(containerName);

        if (await container.CreateIfNotExistsAsync())
        {
                Console.WriteLine("Container [{0}] created.", containerName);
        }
        else
        {
                Console.WriteLine("Container [{0}] exists, skipping creation.",
                    containerName);
        }
}

Dopo la creazione dei contenitori, l'applicazione può caricare i file che verranno usati dalle attività.

Suggerimento

Come usare l'archiviazione BLOB da .NET offre utili informazioni generali sull'uso dei contenitori e dei BLOB di Archiviazione di Azure, quindi è consigliabile prenderne visione quando si inizia a usare Batch.

Passaggio 2: Caricare l'applicazione dell'attività e i file di dati

Caricare l'applicazione dell'attività e i file di input (dati) nei contenitori

Nell'operazione di caricamento dei file, DotNetTutorial definisce prima le raccolte dei percorsi di file di application e input esistenti nel computer locale, quindi carica i file nei contenitori creati nel passaggio precedente.

// Paths to the executable and its dependencies that will be executed by the tasks
List<string> applicationFilePaths = new List<string>
{
    // The DotNetTutorial project includes a project reference to TaskApplication,
    // allowing us to determine the path of the task application binary dynamically
    typeof(TaskApplication.Program).Assembly.Location,
    "Microsoft.WindowsAzure.Storage.dll"
};

// The collection of data files that are to be processed by the tasks
List<string> inputFilePaths = new List<string>
{
    @"..\..\taskdata1.txt",
    @"..\..\taskdata2.txt",
    @"..\..\taskdata3.txt"
};

// Upload the application and its dependencies to Azure Storage. This is the
// application that will process the data files, and will be executed by each
// of the tasks on the compute nodes.
List<ResourceFile> applicationFiles = await UploadFilesToContainerAsync(
    blobClient,
    appContainerName,
    applicationFilePaths);

// Upload the data files. This is the data that will be processed by each of
// the tasks that are executed on the compute nodes within the pool.
List<ResourceFile> inputFiles = await UploadFilesToContainerAsync(
    blobClient,
    inputContainerName,
    inputFilePaths);

Il processo di caricamento interessa due metodi in Program.cs .

  • UploadFilesToContainerAsync: questo metodo restituisce una raccolta di oggetti ResourceFile, illustrati di seguito, e chiama internamente UploadFileToContainerAsync per caricare ogni file passato nel parametro filePaths.
  • UploadFileToContainerAsync: questo metodo esegue effettivamente il caricamento dei file e crea gli oggetti ResourceFile. Dopo il caricamento del file, ottiene una firma di accesso condiviso per il file e restituisce un oggetto ResourceFile che lo rappresenta. Più avanti vengono illustrate anche le firme di accesso condiviso.
private static async Task<ResourceFile> UploadFileToContainerAsync(
    CloudBlobClient blobClient,
    string containerName,
    string filePath)
{
        Console.WriteLine(
            "Uploading file {0} to container [{1}]...", filePath, containerName);

        string blobName = Path.GetFileName(filePath);

        CloudBlobContainer container = blobClient.GetContainerReference(containerName);
        CloudBlockBlob blobData = container.GetBlockBlobReference(blobName);
        await blobData.UploadFromFileAsync(filePath);

        // Set the expiry time and permissions for the blob shared access signature.
        // In this case, no start time is specified, so the shared access signature
        // becomes valid immediately
        SharedAccessBlobPolicy sasConstraints = new SharedAccessBlobPolicy
        {
                SharedAccessExpiryTime = DateTime.UtcNow.AddHours(2),
                Permissions = SharedAccessBlobPermissions.Read
        };

        // Construct the SAS URL for blob
        string sasBlobToken = blobData.GetSharedAccessSignature(sasConstraints);
        string blobSasUri = String.Format("{0}{1}", blobData.Uri, sasBlobToken);

        return new ResourceFile(blobSasUri, blobName);
}

ResourceFiles

Un oggetto ResourceFile fornisce alle attività in Batch l'URL di un file in Archiviazione di Azure che verrà scaricato in un nodo di calcolo prima dell'esecuzione dell'attività. La proprietà ResourceFile.BlobSource specifica l'URL completo del file esistente in Archiviazione di Azure, che può includere anche una firma di accesso condiviso che fornisce l'accesso sicuro al file. Una proprietà ResourceFiles è inclusa nella maggior parte dei tipi di attività in Batch .NET, ad esempio:

L'applicazione di esempio DotNetTutorial non usa il tipo di attività JobPreparationTask o JobReleaseTask, ma altre informazioni in merito sono disponibili in Eseguire attività di preparazione e completamento di processi in nodi di calcolo di Azure Batch.

Firma di accesso condiviso

Le firme di accesso condiviso sono stringhe che, se incluse come parte di un URL, forniscono l'accesso sicuro a contenitori e BLOB in Archiviazione di Azure. L'applicazione DotNetTutorial usa gli URL di firma di accesso condiviso di BLOB e contenitori e illustra come ottenere queste stringhe di firma di accesso condiviso dal servizio di archiviazione.

  • Firme di accesso condiviso di BLOB: l'attività StartTask del pool in DotNetTutorial usa le firme di accesso condiviso dei BLOB durante il download dei file binari dell'applicazione e dei file di dati di input da Archiviazione, come illustrato più avanti nel passaggio 3. Il metodo UploadFileToContainerAsync in Program.cs di DotNetTutorial contiene il codice che ottiene la firma di accesso condiviso di ogni BLOB. L'operazione viene eseguita chiamando CloudBlob.GetSharedAccessSignature.
  • Firme di accesso condiviso di contenitori: quando completa le operazioni sul nodo di calcolo, ogni attività carica il rispettivo file di output nel contenitore output in Archiviazione di Azure. A questo scopo, TaskApplication usa una firma di accesso condiviso del contenitore che fornisce l'accesso in scrittura al contenitore come parte del percorso durante il caricamento del file. Il recupero della firma di accesso condiviso del contenitore viene eseguito in modo analogo al recupero della firma di accesso condiviso del BLOB. In DotNetTutorial si noterà che il metodo helper GetContainerSasUrl chiama a tale scopo CloudBlobContainer.GetSharedAccessSignature. Altre informazioni sul modo in cui TaskApplication usa la firma di accesso condiviso del contenitore sono disponibili più avanti nel "Passaggio 6: Monitorare le attività".

Suggerimento

Per altre informazioni su come fornire l'accesso sicuro ai dati nell'account di archiviazione, vedere la serie in due parti sulle firme di accesso condiviso Firme di accesso condiviso, parte 1: conoscere il modello di firma di accesso condiviso e Firme di accesso condiviso, parte 2: creare e usare una firma di accesso condiviso con l'archiviazione BLOB.

Passaggio 3: Creare un pool di Batch

Creare un pool di Batch

Un pool di Batch è una raccolta di nodi di calcolo (macchine virtuali) in cui Batch esegue le attività di un processo.

Dopo il caricamento dell'applicazione e dei file di dati nell'account di archiviazione con le API di archiviazione di Azure, DotNetTutorial inizia a effettuare chiamate al servizio Batch con le API fornite dalla libreria Batch .NET. Il codice crea prima un BatchClient:

BatchSharedKeyCredentials cred = new BatchSharedKeyCredentials(
    BatchAccountUrl,
    BatchAccountName,
    BatchAccountKey);

using (BatchClient batchClient = BatchClient.Open(cred))
{
    ...

L'esempio crea quindi un pool di nodi di calcolo nell'account Batch con una chiamata a CreatePoolIfNotExistsAsync. CreatePoolIfNotExistsAsync usa il metodo BatchClient.PoolOperations.CreatePool per creare un nuovo pool nel servizio Batch:

private static async Task CreatePoolIfNotExistAsync(BatchClient batchClient, string poolId, IList<ResourceFile> resourceFiles)
{
    CloudPool pool = null;
    try
    {
        Console.WriteLine("Creating pool [{0}]...", poolId);

        // Create the unbound pool. Until we call CloudPool.Commit() or CommitAsync(), no pool is actually created in the
        // Batch service. This CloudPool instance is therefore considered "unbound," and we can modify its properties.
        pool = batchClient.PoolOperations.CreatePool(
            poolId: poolId,
            targetDedicatedComputeNodes: 3,                                             // 3 compute nodes
            virtualMachineSize: "small",                                                // single-core, 1.75 GB memory, 225 GB disk
            cloudServiceConfiguration: new CloudServiceConfiguration(osFamily: "4"));   // Windows Server 2012 R2

        // Create and assign the StartTask that will be executed when compute nodes join the pool.
        // In this case, we copy the StartTask's resource files (that will be automatically downloaded
        // to the node by the StartTask) into the shared directory that all tasks will have access to.
        pool.StartTask = new StartTask
        {
            // Specify a command line for the StartTask that copies the task application files to the
            // node's shared directory. Every compute node in a Batch pool is configured with a number
            // of pre-defined environment variables that can be referenced by commands or applications
            // run by tasks.

            // Since a successful execution of robocopy can return a non-zero exit code (e.g. 1 when one or
            // more files were successfully copied) we need to manually exit with a 0 for Batch to recognize
            // StartTask execution success.
            CommandLine = "cmd /c (robocopy %AZ_BATCH_TASK_WORKING_DIR% %AZ_BATCH_NODE_SHARED_DIR%) ^& IF %ERRORLEVEL% LEQ 1 exit 0",
            ResourceFiles = resourceFiles,
            WaitForSuccess = true
        };

        await pool.CommitAsync();
    }
    catch (BatchException be)
    {
        // Swallow the specific error code PoolExists since that is expected if the pool already exists
        if (be.RequestInformation?.BatchError != null && be.RequestInformation.BatchError.Code == BatchErrorCodeStrings.PoolExists)
        {
            Console.WriteLine("The pool {0} already existed when we tried to create it", poolId);
        }
        else
        {
            throw; // Any other exception is unexpected
        }
    }
}

Quando si crea un pool con CreatePool, si specificano diversi parametri come il numero di nodi di calcolo, le dimensioni dei nodi e il sistema operativo dei nodi. In DotNetTutorial si usa CloudServiceConfiguration per specificare Windows Server 2012 R2 da Servizi cloud.

È anche possibile creare pool di nodi di calcolo costituiti da macchine virtuali di Azure specificando il valore VirtualMachineConfiguration per il pool. È possibile creare un pool di nodi di calcolo di tipo VM da immagini Windows o Linux. L'origine delle immagini di VM può essere una delle seguenti:

Importante

Vengono effettuati addebiti per le risorse di calcolo in Batch. Per ridurre al minimo i costi è possibile ridurre targetDedicatedComputeNodes a 1 prima di eseguire l'esempio.

Oltre alle proprietà relative ai nodi fisici, è possibile specificare anche un oggetto StartTask per il pool. L'attività StartTask viene eseguita in ogni nodo quando questo viene aggiunto al pool e ogni volta che viene riavviato. StartTask è particolarmente utile per l'installazione di applicazioni nei nodi di calcolo prima dell'esecuzione di attività. Ad esempio, se le attività elaborano dati usando script Python, è possibile usare StartTask per installare Python nei nodi di calcolo.

In questa applicazione di esempio, StartTask copia i file scaricati da Archiviazione, specificati usando la proprietà StartTask.ResourceFiles , dalla directory di lavoro di StartTask alla directory condivisa a cui possono accedere tutte le attività in esecuzione nel nodo. Sostanzialmente, TaskApplication.exe e le relative dipendente vengono copiati nella directory condivisa in ogni nodo quando questo viene aggiunto al pool, in modo che qualsiasi attività in esecuzione nel nodo possa accedervi.

Suggerimento

I pacchetti dell'applicazione sono una funzionalità di Azure Batch che offre un altro modo per includere l'applicazione nei nodi di calcolo di un pool. Per informazioni dettagliate, vedere Distribuire le applicazioni nei nodi di calcolo con i pacchetti dell'applicazione Batch.

Nel frammento di codice precedente si può notare anche l'uso di due variabili di ambiente nella proprietà CommandLine di StartTask: %AZ_BATCH_TASK_WORKING_DIR% e %AZ_BATCH_NODE_SHARED_DIR%. Ogni nodo di calcolo in un pool di Batch viene configurato automaticamente con diverse variabili di ambiente specifiche per Batch. Tutti processi eseguiti da un'attività possono accedere a queste variabili di ambiente.

Suggerimento

Per altre informazioni sulle variabili di ambiente disponibili nei nodi di calcolo di un pool di Batch, oltre a informazioni sulle directory di lavoro delle attività, vedere le sezioni Impostazioni di ambiente per le attività e File e directory in Panoramica delle funzionalità di Batch per sviluppatori.

Passaggio 4: Creare un processo di Batch

Creare un processo di Batch

Un processo di Batch è una raccolta di attività ed è associato a un pool di nodi di calcolo. Le attività in un processo vengono eseguite nei nodi di calcolo del pool associato.

Il processo può essere usato non solo per organizzare e tenere traccia delle attività nei carichi di lavoro correlati, ma anche per imporre determinati vincoli, ad esempio il tempo di esecuzione massimo per il processo e, per estensione, per le rispettive attività, nonché per imporre una priorità dei processi rispetto ad altri nell'account Batch. In questo esempio, tuttavia, il processo viene associato solo al pool creato nel Passaggio 3. Non vengono configurate proprietà aggiuntive.

Tutti i processi di Batch sono associati a un pool specifico. Questa associazione indica i nodi in cui verranno eseguite le attività del processo e viene specificata con la proprietà CloudJob.PoolInformation, come illustrato nel frammento di codice seguente.

private static async Task CreateJobAsync(
    BatchClient batchClient,
    string jobId,
    string poolId)
{
    Console.WriteLine("Creating job [{0}]...", jobId);

    CloudJob job = batchClient.JobOperations.CreateJob();
    job.Id = jobId;
    job.PoolInformation = new PoolInformation { PoolId = poolId };

    await job.CommitAsync();
}

Dopo la creazione di un processo, vengono aggiunte attività per l'esecuzione delle operazioni.

Passaggio 5: Aggiungere attività a un processo

Aggiungere attività a un processo
(1) Le attività vengono aggiunte al processo, (2) viene pianificata l'esecuzione delle attività nei nodi e (3) le attività scaricano i file di dati da elaborare

Le attività di Batch sono le singole unità di lavoro eseguite nei nodi di calcolo. Un'attività ha una riga di comando ed esegue gli script o i file eseguibili specificati in questa riga di comando.

Per eseguire effettivamente le operazioni, è necessario aggiungere attività a un processo. Ogni elemento CloudTask viene configurato con una proprietà della riga di comando e, analogamente all'attività StartTask del pool, con oggetti ResourceFiles scaricati dall'attività nel nodo prima dell'esecuzione automatica della rispettiva riga di comando. Nel progetto di esempio DotNetTutorial , ogni attività elabora un solo file. Di conseguenza, la rispettiva raccolta ResourceFiles contiene un singolo elemento.

private static async Task<List<CloudTask>> AddTasksAsync(
    BatchClient batchClient,
    string jobId,
    List<ResourceFile> inputFiles,
    string outputContainerSasUrl)
{
    Console.WriteLine("Adding {0} tasks to job [{1}]...", inputFiles.Count, jobId);

    // Create a collection to hold the tasks that we'll be adding to the job
    List<CloudTask> tasks = new List<CloudTask>();

    // Create each of the tasks. Because we copied the task application to the
    // node's shared directory with the pool's StartTask, we can access it via
    // the shared directory on the node that the task runs on.
    foreach (ResourceFile inputFile in inputFiles)
    {
        string taskId = "topNtask" + inputFiles.IndexOf(inputFile);
        string taskCommandLine = String.Format(
            "cmd /c %AZ_BATCH_NODE_SHARED_DIR%\\TaskApplication.exe {0} 3 \"{1}\"",
            inputFile.FilePath,
            outputContainerSasUrl);

        CloudTask task = new CloudTask(taskId, taskCommandLine);
        task.ResourceFiles = new List<ResourceFile> { inputFile };
        tasks.Add(task);
    }

    // Add the tasks as a collection, as opposed to issuing a separate AddTask call
    // for each. Bulk task submission helps to ensure efficient underlying API calls
    // to the Batch service.
    await batchClient.JobOperations.AddTaskAsync(jobId, tasks);

    return tasks;
}

Importante

Quando accedono a variabili di ambiente come %AZ_BATCH_NODE_SHARED_DIR% o eseguono un'applicazione non presente nell'elemento PATH del nodo, le righe di comando dell'attività devono avere il prefisso cmd /c. In questo modo verrà eseguito esplicitamente l'interprete dei comandi, indicando che è necessario terminare il processo dopo l'esecuzione del comando. Questo requisito è superfluo se le attività eseguono un'applicazione nell'elemento PATH del nodo, ad esempio robocopy.exe o powershell.exe, e non vengono usate variabili di ambiente.

Nel ciclo foreach del frammento di codice precedente è possibile notare che la riga di comando per l'attività è costruita in modo da passare a TaskApplication.exetre argomenti della riga di comando:

  1. Il primo argomento è il percorso del file da elaborare. Corrisponde al percorso locale del file esistente sul nodo. Durante la creazione iniziale dell'oggetto ResourceFile in UploadFileToContainerAsync , per questa proprietà (come parametro per il costruttore ResourceFile) è stato usato il nome file. Ciò indica che il file si trova nella stessa directory di TaskApplication.exe.
  2. Il secondo argomento specifica che le prime N parole devono essere scritte nel file di output. Queste informazioni sono hardcoded nell'esempio in modo che nel file di output vengano scritte le prime tre parole.
  3. Il terzo argomento è la firma di accesso condiviso che consente l'accesso in scrittura al contenitore output in Archiviazione di Azure. TaskApplication.exe usa questo URL della firma di accesso condiviso durante il caricamento del file di output in Archiviazione di Azure. Il codice corrispondente è disponibile nel metodo UploadFileToContainer del file Program.cs del progetto TaskApplication:
// NOTE: From project TaskApplication Program.cs

private static void UploadFileToContainer(string filePath, string containerSas)
{
        string blobName = Path.GetFileName(filePath);

        // Obtain a reference to the container using the SAS URI.
        CloudBlobContainer container = new CloudBlobContainer(new Uri(containerSas));

        // Upload the file (as a new blob) to the container
        try
        {
                CloudBlockBlob blob = container.GetBlockBlobReference(blobName);
                blob.UploadFromFile(filePath);

                Console.WriteLine("Write operation succeeded for SAS URL " + containerSas);
                Console.WriteLine();
        }
        catch (StorageException e)
        {

                Console.WriteLine("Write operation failed for SAS URL " + containerSas);
                Console.WriteLine("Additional error information: " + e.Message);
                Console.WriteLine();

                // Indicate that a failure has occurred so that when the Batch service
                // sets the CloudTask.ExecutionInformation.ExitCode for the task that
                // executed this application, it properly indicates that there was a
                // problem with the task.
                Environment.ExitCode = -1;
        }
}

Passaggio 6: Monitorare le attività

Monitorare le attività
(1) L'applicazione client monitora le attività per verificare lo stato di completamento e di esito positivo e (2) le attività caricano i dati dei risultati in Archiviazione di Azure

Quando le attività vengono aggiunte a un processo, vengono accodate automaticamente e ne viene pianificata l'esecuzione nei nodi di calcolo entro il pool associato al progetto. In base alle impostazioni specificate, Batch gestisce tutte le operazioni di accodamento, pianificazione, ripetizione di tentativi dell'attività e tutte le altre operazioni amministrative relative all'attività.

Sono disponibili molti approcci al monitoraggio dell'esecuzione delle attività. DotNetTutorial illustra un semplice esempio che segnala solo gli stati di completamento ed esito positivo o negativo dell'attività. Nel metodo MonitorTasks in Program.cs di DotNetTutorial sono presenti tre concetti di Batch .NET che meritano un approfondimento. I concetti sono elencati di seguito nell'ordine in cui appaiono:

  1. ODATADetailLevel: specificare ODATADetailLevel nelle operazioni di tipo elenco, come ad esempio il recupero di un elenco delle attività di un processo, è essenziale per assicurare prestazioni ottimali per l'applicazione Batch. Se si prevede di monitorare in qualche modo lo stato nelle applicazioni Batch, vedere anche Eseguire query sul servizio Azure Batch in modo efficiente .
  2. TaskStateMonitor: TaskStateMonitor fornisce alle applicazioni Batch .NET le utilità helper per monitorare gli stati delle attività. In MonitorTasks, DotNetTutorial attende che tutte le attività raggiungano lo stato TaskState.Completed entro un limite di tempo, quindi termina il processo.
  3. TerminateJobAsync: la terminazione di un processo con JobOperations.TerminateJobAsync (o il valore JobOperations.TerminateJob di blocco) contrassegna il processo come completato. Ciò è essenziale se la soluzione Batch usa JobReleaseTask, un tipo speciale di attività descritto in Attività di preparazione e completamento di processi.

Il metodo MonitorTasks di Program.cs di DotNetTutorial è indicato di seguito:

private static async Task<bool> MonitorTasks(
    BatchClient batchClient,
    string jobId,
    TimeSpan timeout)
{
    bool allTasksSuccessful = true;
    const string successMessage = "All tasks reached state Completed.";
    const string failureMessage = "One or more tasks failed to reach the Completed state within the timeout period.";

    // Obtain the collection of tasks currently managed by the job. Note that we use
    // a detail level to  specify that only the "id" property of each task should be
    // populated. Using a detail level for all list operations helps to lower
    // response time from the Batch service.
    ODATADetailLevel detail = new ODATADetailLevel(selectClause: "id");
    List<CloudTask> tasks =
        await batchClient.JobOperations.ListTasks(JobId, detail).ToListAsync();

    Console.WriteLine("Awaiting task completion, timeout in {0}...",
        timeout.ToString());

    // We use a TaskStateMonitor to monitor the state of our tasks. In this case, we
    // will wait for all tasks to reach the Completed state.
    TaskStateMonitor taskStateMonitor
        = batchClient.Utilities.CreateTaskStateMonitor();

    try
    {
        await taskStateMonitor.WhenAll(tasks, TaskState.Completed, timeout);
    }
    catch (TimeoutException)
    {
        await batchClient.JobOperations.TerminateJobAsync(jobId, failureMessage);
        Console.WriteLine(failureMessage);
        return false;
    }

    await batchClient.JobOperations.TerminateJobAsync(jobId, successMessage);

    // All tasks have reached the "Completed" state, however, this does not
    // guarantee all tasks completed successfully. Here we further check each task's
    // ExecutionInfo property to ensure that it did not encounter a failure
    // or return a non-zero exit code.

    // Update the detail level to populate only the task id and executionInfo
    // properties. We refresh the tasks below, and need only this information for
    // each task.
    detail.SelectClause = "id, executionInfo";

    foreach (CloudTask task in tasks)
    {
        // Populate the task's properties with the latest info from the Batch service
        await task.RefreshAsync(detail);

        if (task.ExecutionInformation.Result == TaskExecutionResult.Failure)
        {
            // A task with failure information set indicates there was a problem with the task. It is important to note that
            // the task's state can be "Completed," yet still have encountered a failure.

            allTasksSuccessful = false;

            Console.WriteLine("WARNING: Task [{0}] encountered a failure: {1}", task.Id, task.ExecutionInformation.FailureInformation.Message);
            if (task.ExecutionInformation.ExitCode != 0)
            {
                // A non-zero exit code may indicate that the application executed by the task encountered an error
                // during execution. As not every application returns non-zero on failure by default (e.g. robocopy),
                // your implementation of error checking may differ from this example.

                Console.WriteLine("WARNING: Task [{0}] returned a non-zero exit code - this may indicate task execution or completion failure.", task.Id);
            }
        }
    }

    if (allTasksSuccessful)
    {
        Console.WriteLine("Success! All tasks completed successfully within the specified timeout period.");
    }

    return allTasksSuccessful;
}

Passaggio 7: Scaricare l'output dell'attività

Scaricare l'output delle attività dal servizio di archiviazione

Dopo il completamento del processo, l'output delle attività può essere scaricato da Archiviazione di Azure con una chiamata a DownloadBlobsFromContainerAsync in Program.cs di DotNetTutorial:

private static async Task DownloadBlobsFromContainerAsync(
    CloudBlobClient blobClient,
    string containerName,
    string directoryPath)
{
        Console.WriteLine("Downloading all files from container [{0}]...", containerName);

        // Retrieve a reference to a previously created container
        CloudBlobContainer container = blobClient.GetContainerReference(containerName);

        // Get a flat listing of all the block blobs in the specified container
        foreach (IListBlobItem item in container.ListBlobs(
                    prefix: null,
                    useFlatBlobListing: true))
        {
                // Retrieve reference to the current blob
                CloudBlob blob = (CloudBlob)item;

                // Save blob contents to a file in the specified folder
                string localOutputFile = Path.Combine(directoryPath, blob.Name);
                await blob.DownloadToFileAsync(localOutputFile, FileMode.Create);
        }

        Console.WriteLine("All files downloaded to {0}", directoryPath);
}

Nota

La chiamata a DownloadBlobsFromContainerAsync nell'applicazione DotNetTutorial specifica che i file devono essere scaricati nella cartella %TEMP%. È possibile modificare questo percorso di output.

Passaggio 8: Eliminare i contenitori

Poiché vengono effettuati addebiti per i dati che risiedono in Archiviazione di Azure, è consigliabile rimuovere i BLOB non più necessari per i processi di Batch. In Program.cs di DotNetTutorial, questa operazione viene eseguita con tre chiamate al metodo helper DeleteContainerAsync:

// Clean up Storage resources
await DeleteContainerAsync(blobClient, appContainerName);
await DeleteContainerAsync(blobClient, inputContainerName);
await DeleteContainerAsync(blobClient, outputContainerName);

Il metodo ottiene semplicemente un riferimento al contenitore e quindi chiama CloudBlobContainer.DeleteIfExistsAsync:

private static async Task DeleteContainerAsync(
    CloudBlobClient blobClient,
    string containerName)
{
    CloudBlobContainer container = blobClient.GetContainerReference(containerName);

    if (await container.DeleteIfExistsAsync())
    {
        Console.WriteLine("Container [{0}] deleted.", containerName);
    }
    else
    {
        Console.WriteLine("Container [{0}] does not exist, skipping deletion.",
            containerName);
    }
}

Passaggio 9: Eliminare il processo e il pool

Nel passaggio finale viene richiesto di eliminare il processo e il pool creati dall'applicazione DotNetTutorial. Anche se non vengono addebitati costi per i processi e per le attività, vengono invece addebiti costi per i nodi di calcolo. È quindi consigliabile allocare i nodi solo in base alla necessità. L'eliminazione dei pool inutilizzati può fare parte del processo di manutenzione.

Gli elementi JobOperations e PoolOperations di BatchClient includono metodi di eliminazione corrispondenti, che vengono chiamati se l'utente conferma l'eliminazione:

// Clean up the resources we've created in the Batch account if the user so chooses
Console.WriteLine();
Console.WriteLine("Delete job? [yes] no");
string response = Console.ReadLine().ToLower();
if (response != "n" && response != "no")
{
    await batchClient.JobOperations.DeleteJobAsync(JobId);
}

Console.WriteLine("Delete pool? [yes] no");
response = Console.ReadLine();
if (response != "n" && response != "no")
{
    await batchClient.PoolOperations.DeletePoolAsync(PoolId);
}

Importante

Occorre ricordare che vengono effettuati addebiti per le risorse di calcolo e che l'eliminazione di pool non usati consente di ridurre al minimo i costi. Si noti anche che l'eliminazione di un pool comporta l'eliminazione di tutti i nodi di calcolo in quel pool e che eventuali dati disponibili nei nodi non potranno essere più recuperati dopo l'eliminazione del pool.

Eseguire l'esempio DotNetTutorial

Quando si esegue l'applicazione di esempio, l'output della console sarà simile al seguente. Durante l'esecuzione si riscontrerà una pausa in corrispondenza di Awaiting task completion, timeout in 00:30:00... mentre vengono avviati i nodi di calcolo del pool. Usare il portale di Azure per monitorare il pool, i nodi di calcolo, il processo e le attività durante e dopo l'esecuzione. Usare il portale di Azure o Azure Storage Explorer per visualizzare le risorse di archiviazione (contenitori e BLOB) create dall'applicazione.

Se si esegue l'applicazione con la configurazione predefinita, il tempo di esecuzione tipico è di circa 5 minuti .

Sample start: 1/8/2016 09:42:58 AM

Container [application] created.
Container [input] created.
Container [output] created.
Uploading file C:\repos\azure-batch-samples\CSharp\ArticleProjects\DotNetTutorial\bin\Debug\TaskApplication.exe to container [application]...
Uploading file Microsoft.WindowsAzure.Storage.dll to container [application]...
Uploading file ..\..\taskdata1.txt to container [input]...
Uploading file ..\..\taskdata2.txt to container [input]...
Uploading file ..\..\taskdata3.txt to container [input]...
Creating pool [DotNetTutorialPool]...
Creating job [DotNetTutorialJob]...
Adding 3 tasks to job [DotNetTutorialJob]...
Awaiting task completion, timeout in 00:30:00...
Success! All tasks completed successfully within the specified timeout period.
Downloading all files from container [output]...
All files downloaded to C:\Users\USERNAME\AppData\Local\Temp
Container [application] deleted.
Container [input] deleted.
Container [output] deleted.

Sample end: 1/8/2016 09:47:47 AM
Elapsed time: 00:04:48.5358142

Delete job? [yes] no: yes
Delete pool? [yes] no: yes

Sample complete, hit ENTER to exit...

Passaggi successivi

È possibile modificare DotNetTutorial e TaskApplication per sperimentare scenari di calcolo diversi. Si può, ad esempio, provare ad aggiungere un ritardo di esecuzione in TaskApplication, ad esempio con Thread.Sleep, per simulare attività con esecuzione prolungata e monitorarle nel portale. Provare ad aggiungere altre attività o a modificare il numero di nodi di calcolo. Aggiungere la logica per la ricerca e l'uso di un pool esistente per ridurre il tempo di esecuzione. Suggerimento: vedere ArticleHelpers.cs nel progetto Microsoft.Azure.Batch.Samples.Common in azure-batch-samples.

Dopo avere acquisito familiarità con il flusso di lavoro di base di una soluzione Batch, è possibile esaminare in dettaglio le funzionalità aggiuntive del servizio Batch.