Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add F# and VB.NET samples #2046

Merged
merged 6 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/wordlist.txt
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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/"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good choice :)

}

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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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