Skip to content

Commit

Permalink
Add F# and VB.NET samples (#2046)
Browse files Browse the repository at this point in the history
Add documentation for using Polly with F# and VB.

Co-authored-by: Stuart Lang <stuart.b.lang@gmail.com>
Co-authored-by: Chet Husk <573979+baronfel@users.noreply.github.com>
Co-authored-by: Jimmy Byrd <1490044+theangrybyrd@users.noreply.github.com>
  • Loading branch information
4 people committed Apr 18, 2024
1 parent f537f55 commit 39cc454
Show file tree
Hide file tree
Showing 10 changed files with 329 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .github/wordlist.txt
@@ -1,6 +1,7 @@
alloc
apis
ASP.NET
astask
async
azurefunctions
bcl
Expand All @@ -18,7 +19,9 @@ enricher
eshoponcontainers
extensibility
flurl
fs
hangfire
interop
jetbrains
jitter
jittered
Expand Down Expand Up @@ -67,6 +70,7 @@ timingpolicy
ui
unhandled
uwp
valuetask
waitandretry
wpf
xunit
2 changes: 2 additions & 0 deletions Directory.Packages.props
Expand Up @@ -9,7 +9,9 @@
<PackageVersion Include="coverlet.msbuild" Version="6.0.1" />
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
<PackageVersion Include="FSharp.Core" Version="8.0.200" />
<PackageVersion Include="GitHubActionsTestLogger" Version="2.3.3" />
<PackageVersion Include="IcedTasks" Version="0.11.4" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
<PackageVersion Include="Microsoft.Bcl.TimeProvider" Version="$(MicrosoftExtensionsVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
Expand Down
4 changes: 4 additions & 0 deletions docs/getting-started.md
Expand Up @@ -23,6 +23,10 @@ await pipeline.ExecuteAsync(static async token => { /* Your custom logic goes he
```
<!-- endSnippet -->

> [!NOTE]
> Asynchronous methods in the Polly API return `ValueTask` or `ValueTask<T>` instead of `Task` or `Task<T>`.
> If you are using Polly in Visual Basic or F#, please read [Use with F# and Visual Basic](use-with-fsharp-and-visual-basic.md) for more information.
## Dependency injection

If you prefer to define resilience pipelines using [`IServiceCollection`](https://learn.microsoft.com/dotnet/api/microsoft.extensions.dependencyinjection.iservicecollection), you'll need to install the [Polly.Extensions](https://www.nuget.org/packages/Polly.Extensions/) package:
Expand Down
3 changes: 3 additions & 0 deletions docs/toc.yml
Expand Up @@ -44,6 +44,9 @@
- name: Behavior
href: chaos/behavior.md

- name: Use with F# and Visual Basic
href: use-with-fsharp-and-visual-basic.md

- name: Advanced topics
expanded: true
items:
Expand Down
151 changes: 151 additions & 0 deletions docs/use-with-fsharp-and-visual-basic.md
@@ -0,0 +1,151 @@
# Use with F# and Visual Basic

Asynchronous methods in the Polly.Core API return either `ValueTask` or `ValueTask<T>`
instead of `Task` or `Task<T>`. This is because Polly v8 was designed to be optimized
for high performance and uses `ValueTask` to avoid unnecessary allocations.

One downside to this choice is that in Visual Basic and F#, it is not possible to directly
await a method that returns `ValueTask` or `ValueTask<T>`, instead requiring the use of
`Task` and `Task<T>`.

A proposal to support awaiting `ValueTask` can be found in F# language design repository:
[[RFC FS-1021 Discussion] Support Interop with ValueTask in Async Type][fsharp-fslang-design-118].

To work around this limitation, you can use the [`AsTask()`][valuetask-astask] method to convert a
`ValueTask` to a `Task` in F# and Visual Basic. This does however introduce an allocation and make
the code a bit more difficult to work with compared to C#.

Examples of such conversions are shown below.

## F\#

```fsharp
open FSharp.Control
open System
open System.Threading
open System.Threading.Tasks
open IcedTasks
open Polly
let getBestFilmAsync token =
task {
do! Task.Delay(1000, token)
return "https://www.imdb.com/title/tt0080684/"
}
let demo () =
task {
// The ResiliencePipelineBuilder creates a ResiliencePipeline
// that can be executed synchronously or asynchronously
// and for both void and result-returning user-callbacks.
let pipeline =
ResiliencePipelineBuilder()
.AddTimeout(TimeSpan.FromSeconds(5))
.Build()
let token = CancellationToken.None
// Synchronously
pipeline.Execute(fun () -> printfn "Hello, world!")
// Asynchronously
// Note that Polly expects a ValueTask to be returned, so the function uses the valueTask builder
// from IcedTasks to make it easier to use ValueTask. See https://github.com/TheAngryByrd/IcedTasks.
do! pipeline.ExecuteAsync(
fun token ->
valueTask {
printfn "Hello, world! Waiting for 2 seconds..."
do! Task.Delay(1000, token)
printfn "Wait complete."
}
, token
)
// Synchronously with result
let someResult = pipeline.Execute(fun token -> "some-result")
// Asynchronously with result
// Note that Polly expects a ValueTask<T> to be returned, so the function uses the valueTask builder
// from IcedTasks to make it easier to use ValueTask<T>. See https://github.com/TheAngryByrd/IcedTasks.
let! bestFilm = pipeline.ExecuteAsync(
fun token ->
valueTask {
let! url = getBestFilmAsync(token)
return url
}
, token
)
printfn $"Link to the best film: {bestFilm}"
}
```

[Source][sample-fsharp]

## Visual Basic

```vb
Imports System.Threading
Imports Polly

Module Program
Sub Main()
Demo().Wait()
End Sub

Async Function Demo() As Task
' The ResiliencePipelineBuilder creates a ResiliencePipeline
' that can be executed synchronously or asynchronously
' and for both void and result-returning user-callbacks.
Dim pipeline = New ResiliencePipelineBuilder().AddTimeout(TimeSpan.FromSeconds(5)).Build()

' Synchronously
pipeline.Execute(Sub()
Console.WriteLine("Hello, world!")
End Sub)

' Asynchronously
' Note that the function is wrapped in a ValueTask for Polly to use as VB.NET cannot
' await ValueTask directly, and AsTask() is used to convert the ValueTask returned by
' ExecuteAsync() to a Task so it can be awaited.
Await pipeline.ExecuteAsync(Function(token)
Return New ValueTask(GreetAndWaitAsync(token))
End Function,
CancellationToken.None).AsTask()

' Synchronously with result
Dim someResult = pipeline.Execute(Function(token)
Return "some-result"
End Function)

' Asynchronously with result
' Note that the function is wrapped in a ValueTask(Of String) for Polly to use as VB.NET cannot
' await ValueTask directly, and AsTask() is used to convert the ValueTask(Of String) returned by
' ExecuteAsync() to a Task(Of String) so it can be awaited.
Dim bestFilm = Await pipeline.ExecuteAsync(Function(token)
Return New ValueTask(Of String)(GetBestFilmAsync(token))
End Function,
CancellationToken.None).AsTask()

Console.WriteLine("Link to the best film: {0}", bestFilm)

End Function

Async Function GreetAndWaitAsync(token As CancellationToken) As Task
Console.WriteLine("Hello, world! Waiting for 1 second...")
Await Task.Delay(1000, token)
End Function

Async Function GetBestFilmAsync(token As CancellationToken) As Task(Of String)
Await Task.Delay(1000, token)
Return "https://www.imdb.com/title/tt0080684/"
End Function
End Module
```

[Source][sample-vb]

[fsharp-fslang-design-118]: https://github.com/fsharp/fslang-design/discussions/118
[valuetask-astask]: https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask.astask
[sample-fsharp]: https://github.com/App-vNext/Polly/tree/main/samples/Intro.FSharp
[sample-vb]: https://github.com/App-vNext/Polly/tree/main/samples/Intro.VisualBasic
20 changes: 20 additions & 0 deletions samples/Intro.FSharp/Intro.FSharp.fsproj
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="FSharp.Core" />
<PackageReference Include="IcedTasks" />
<PackageReference Include="Polly.Core" />
</ItemGroup>

</Project>
63 changes: 63 additions & 0 deletions samples/Intro.FSharp/Program.fs
@@ -0,0 +1,63 @@
open FSharp.Control
open System
open System.Threading
open System.Threading.Tasks
open IcedTasks
open Polly

let getBestFilmAsync token =
task {
do! Task.Delay(1000, token)
return "https://www.imdb.com/title/tt0080684/"
}

let demo () =
task {
// The ResiliencePipelineBuilder creates a ResiliencePipeline
// that can be executed synchronously or asynchronously
// and for both void and result-returning user-callbacks.
let pipeline =
ResiliencePipelineBuilder()
.AddTimeout(TimeSpan.FromSeconds(5))
.Build()

let token = CancellationToken.None

// Synchronously
pipeline.Execute(fun () -> printfn "Hello, world!")

// Asynchronously
// Note that Polly expects a ValueTask to be returned, so the function uses the valueTask builder
// from IcedTasks to make it easier to use ValueTask. See https://github.com/TheAngryByrd/IcedTasks.
do! pipeline.ExecuteAsync(
fun token ->
valueTask {
printfn "Hello, world! Waiting for 2 seconds..."
do! Task.Delay(1000, token)
printfn "Wait complete."
}
, token
)

// Synchronously with result
let someResult = pipeline.Execute(fun token -> "some-result")

// Asynchronously with result
// Note that Polly expects a ValueTask<T> to be returned, so the function uses the valueTask builder
// from IcedTasks to make it easier to use ValueTask<T>. See https://github.com/TheAngryByrd/IcedTasks.
let! bestFilm = pipeline.ExecuteAsync(
fun token ->
valueTask {
let! url = getBestFilmAsync(token)
return url
}
, token
)

printfn $"Link to the best film: {bestFilm}"
}

[<EntryPoint>]
let main _ =
demo().Wait()
0
14 changes: 14 additions & 0 deletions samples/Intro.VisualBasic/Intro.VisualBasic.vbproj
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Polly.Core" />
</ItemGroup>

</Project>
56 changes: 56 additions & 0 deletions samples/Intro.VisualBasic/Program.vb
@@ -0,0 +1,56 @@
Imports System.Threading
Imports Polly

Module Program
Sub Main()
Demo().Wait()
End Sub

Async Function Demo() As Task
' The ResiliencePipelineBuilder creates a ResiliencePipeline
' that can be executed synchronously or asynchronously
' and for both void and result-returning user-callbacks.
Dim pipeline = New ResiliencePipelineBuilder().AddTimeout(TimeSpan.FromSeconds(5)).Build()

' Synchronously
pipeline.Execute(Sub()
Console.WriteLine("Hello, world!")
End Sub)

' Asynchronously
' Note that the function is wrapped in a ValueTask for Polly to use as VB.NET cannot
' await ValueTask directly, and AsTask() is used to convert the ValueTask returned by
' ExecuteAsync() to a Task so it can be awaited.
Await pipeline.ExecuteAsync(Function(token)
Return New ValueTask(GreetAndWaitAsync(token))
End Function,
CancellationToken.None).AsTask()

' Synchronously with result
Dim someResult = pipeline.Execute(Function(token)
Return "some-result"
End Function)

' Asynchronously with result
' Note that the function is wrapped in a ValueTask(Of String) for Polly to use as VB.NET cannot
' await ValueTask directly, and AsTask() is used to convert the ValueTask(Of String) returned by
' ExecuteAsync() to a Task(Of String) so it can be awaited.
Dim bestFilm = Await pipeline.ExecuteAsync(Function(token)
Return New ValueTask(Of String)(GetBestFilmAsync(token))
End Function,
CancellationToken.None).AsTask()

Console.WriteLine("Link to the best film: {0}", bestFilm)

End Function

Async Function GreetAndWaitAsync(token As CancellationToken) As Task
Console.WriteLine("Hello, world! Waiting for 1 second...")
Await Task.Delay(1000, token)
End Function

Async Function GetBestFilmAsync(token As CancellationToken) As Task(Of String)
Await Task.Delay(1000, token)
Return "https://www.imdb.com/title/tt0080684/"
End Function
End Module
12 changes: 12 additions & 0 deletions samples/Samples.sln
Expand Up @@ -24,6 +24,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DependencyInjection", "Depe
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Chaos", "Chaos\Chaos.csproj", "{A296E17C-B95F-4B15-8B0D-9D6CC0929A1D}"
EndProject
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Intro.VisualBasic", "Intro.VisualBasic\Intro.VisualBasic.vbproj", "{10F1C68E-DBF8-43DE-8A72-3EB4491ECD9C}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Intro.FSharp", "Intro.FSharp\Intro.FSharp.fsproj", "{2C0F3F7F-63ED-472B-80B7-905618B07714}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -54,6 +58,14 @@ Global
{A296E17C-B95F-4B15-8B0D-9D6CC0929A1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A296E17C-B95F-4B15-8B0D-9D6CC0929A1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A296E17C-B95F-4B15-8B0D-9D6CC0929A1D}.Release|Any CPU.Build.0 = Release|Any CPU
{10F1C68E-DBF8-43DE-8A72-3EB4491ECD9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{10F1C68E-DBF8-43DE-8A72-3EB4491ECD9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{10F1C68E-DBF8-43DE-8A72-3EB4491ECD9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{10F1C68E-DBF8-43DE-8A72-3EB4491ECD9C}.Release|Any CPU.Build.0 = Release|Any CPU
{2C0F3F7F-63ED-472B-80B7-905618B07714}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2C0F3F7F-63ED-472B-80B7-905618B07714}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C0F3F7F-63ED-472B-80B7-905618B07714}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C0F3F7F-63ED-472B-80B7-905618B07714}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down

0 comments on commit 39cc454

Please sign in to comment.