Skip to content

Commit ed93447

Browse files
Merge pull request #312946 from hhunter-ms/hh-454114
[Durable][UUF] Add isolated examples for fan in fan out
2 parents 3d2c66e + 650375d commit ed93447

1 file changed

Lines changed: 133 additions & 0 deletions

File tree

articles/azure-functions/durable/durable-functions-fan-in-fan-out.md

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,55 @@ Here is the code that implements the orchestrator function:
9292

9393
[!code-csharp[Main](~/samples-durable-functions/samples/precompiled/BackupSiteContent.cs?range=16-42)]
9494

95+
> [!NOTE]
96+
> The sample linked in the prerequisites (`samples/precompiled`) uses the in-process model. The following **Isolated model** sections show equivalent code for the .NET isolated worker model.
97+
9598
Notice the `await Task.WhenAll(tasks);` line. The code doesn't await the individual calls to `E2_CopyFileToBlob`, so they run in parallel. When the orchestrator passes the task array to `Task.WhenAll`, it returns a task that doesn't complete until all copy operations complete. If you're familiar with the Task Parallel Library (TPL) in .NET, this pattern is familiar. The difference is that these tasks could be running on multiple virtual machines concurrently, and the Durable Functions extension ensures that the end-to-end execution is resilient to process recycling.
9699

97100
After the orchestrator awaits `Task.WhenAll`, all function calls are complete and return values. Each call to `E2_CopyFileToBlob` returns the number of bytes uploaded. Calculate the total by adding the return values.
98101

102+
<br>
103+
104+
<details>
105+
<summary><b>Isolated model</b></summary>
106+
107+
```csharp
108+
using System.IO;
109+
using System.Linq;
110+
using System.Threading.Tasks;
111+
using Microsoft.Azure.Functions.Worker;
112+
using Microsoft.DurableTask;
113+
114+
namespace SampleApp;
115+
116+
public static class BackupSiteContent
117+
{
118+
[Function("E2_BackupSiteContent")]
119+
public static async Task<long> Run(
120+
[OrchestrationTrigger] TaskOrchestrationContext context)
121+
{
122+
string rootDirectory = context.GetInput<string>()?.Trim();
123+
if (string.IsNullOrEmpty(rootDirectory))
124+
{
125+
rootDirectory = Directory.GetParent(typeof(BackupSiteContent).Assembly.Location)!.FullName;
126+
}
127+
128+
string[] files = await context.CallActivityAsync<string[]>("E2_GetFileList", rootDirectory);
129+
130+
Task<long>[] tasks = files
131+
.Select(file => context.CallActivityAsync<long>("E2_CopyFileToBlob", file))
132+
.ToArray();
133+
134+
long[] results = await Task.WhenAll(tasks);
135+
return results.Sum();
136+
}
137+
}
138+
```
139+
140+
</details>
141+
142+
<br>
143+
99144
# [JavaScript](#tab/javascript)
100145

101146
<details>
@@ -321,6 +366,40 @@ The helper activity functions are regular functions that use the `activityTrigge
321366

322367
[!code-csharp[Main](~/samples-durable-functions/samples/precompiled/BackupSiteContent.cs?range=44-54)]
323368

369+
<br>
370+
371+
<details>
372+
<summary><b>Isolated model</b></summary>
373+
374+
```csharp
375+
using System.IO;
376+
using Microsoft.Azure.Functions.Worker;
377+
using Microsoft.Extensions.Logging;
378+
379+
namespace SampleApp;
380+
381+
public static class BackupSiteContent
382+
{
383+
[Function("E2_GetFileList")]
384+
public static string[] GetFileList(
385+
[ActivityTrigger] string rootDirectory,
386+
FunctionContext executionContext)
387+
{
388+
ILogger logger = executionContext.GetLogger("E2_GetFileList");
389+
logger.LogInformation("Searching for files under '{RootDirectory}'...", rootDirectory);
390+
391+
string[] files = Directory.GetFiles(rootDirectory, "*", SearchOption.AllDirectories);
392+
logger.LogInformation("Found {FileCount} file(s) under {RootDirectory}.", files.Length, rootDirectory);
393+
394+
return files;
395+
}
396+
}
397+
```
398+
399+
</details>
400+
401+
<br>
402+
324403
# [JavaScript](#tab/javascript)
325404

326405
<details>
@@ -385,6 +464,60 @@ Java sample coming soon.
385464
386465
The function uses Azure Functions binding features like the [`Binder` parameter](../functions-dotnet-class-library.md#binding-at-runtime). You don't need those details for this walkthrough.
387466

467+
<br>
468+
469+
<details>
470+
<summary><b>Isolated model</b></summary>
471+
472+
```csharp
473+
using System;
474+
using System.IO;
475+
using System.Threading.Tasks;
476+
using Azure.Storage.Blobs;
477+
using Microsoft.Azure.Functions.Worker;
478+
using Microsoft.Extensions.Logging;
479+
480+
namespace SampleApp;
481+
482+
public static class BackupSiteContent
483+
{
484+
[Function("E2_CopyFileToBlob")]
485+
public static async Task<long> CopyFileToBlob(
486+
[ActivityTrigger] string filePath,
487+
FunctionContext executionContext)
488+
{
489+
ILogger logger = executionContext.GetLogger("E2_CopyFileToBlob");
490+
long byteCount = new FileInfo(filePath).Length;
491+
492+
string blobPath = filePath
493+
.Substring(Path.GetPathRoot(filePath)!.Length)
494+
.Replace('\\', '/');
495+
string outputLocation = $"backups/{blobPath}";
496+
497+
string? connectionString = Environment.GetEnvironmentVariable("AzureWebJobsStorage");
498+
if (string.IsNullOrEmpty(connectionString))
499+
{
500+
throw new InvalidOperationException("AzureWebJobsStorage is not configured.");
501+
}
502+
503+
BlobContainerClient containerClient = new(connectionString, "backups");
504+
await containerClient.CreateIfNotExistsAsync();
505+
BlobClient blobClient = containerClient.GetBlobClient(blobPath);
506+
507+
logger.LogInformation("Copying '{FilePath}' to '{OutputLocation}'. Total bytes = {ByteCount}.", filePath, outputLocation, byteCount);
508+
509+
await using Stream source = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
510+
await blobClient.UploadAsync(source, overwrite: true);
511+
512+
return byteCount;
513+
}
514+
}
515+
```
516+
517+
</details>
518+
519+
<br>
520+
388521
# [JavaScript](#tab/javascript)
389522

390523
<details>

0 commit comments

Comments
 (0)