Skip to content

Commit 39cc454

Browse files
martincostelloslang25baronfelTheAngryByrd
authored
Add F# and VB.NET samples (#2046)
Add documentation for using Polly with F# and VB. Co-authored-by: Stuart Lang <[email protected]> Co-authored-by: Chet Husk <[email protected]> Co-authored-by: Jimmy Byrd <[email protected]>
1 parent f537f55 commit 39cc454

File tree

10 files changed

+329
-0
lines changed

10 files changed

+329
-0
lines changed

.github/wordlist.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
alloc
22
apis
33
ASP.NET
4+
astask
45
async
56
azurefunctions
67
bcl
@@ -18,7 +19,9 @@ enricher
1819
eshoponcontainers
1920
extensibility
2021
flurl
22+
fs
2123
hangfire
24+
interop
2225
jetbrains
2326
jitter
2427
jittered
@@ -67,6 +70,7 @@ timingpolicy
6770
ui
6871
unhandled
6972
uwp
73+
valuetask
7074
waitandretry
7175
wpf
7276
xunit

Directory.Packages.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
<PackageVersion Include="coverlet.msbuild" Version="6.0.1" />
1010
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
1111
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
12+
<PackageVersion Include="FSharp.Core" Version="8.0.200" />
1213
<PackageVersion Include="GitHubActionsTestLogger" Version="2.3.3" />
14+
<PackageVersion Include="IcedTasks" Version="0.11.4" />
1315
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
1416
<PackageVersion Include="Microsoft.Bcl.TimeProvider" Version="$(MicrosoftExtensionsVersion)" />
1517
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />

docs/getting-started.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ await pipeline.ExecuteAsync(static async token => { /* Your custom logic goes he
2323
```
2424
<!-- endSnippet -->
2525

26+
> [!NOTE]
27+
> Asynchronous methods in the Polly API return `ValueTask` or `ValueTask<T>` instead of `Task` or `Task<T>`.
28+
> 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.
29+
2630
## Dependency injection
2731

2832
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:

docs/toc.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
- name: Behavior
4545
href: chaos/behavior.md
4646

47+
- name: Use with F# and Visual Basic
48+
href: use-with-fsharp-and-visual-basic.md
49+
4750
- name: Advanced topics
4851
expanded: true
4952
items:
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# Use with F# and Visual Basic
2+
3+
Asynchronous methods in the Polly.Core API return either `ValueTask` or `ValueTask<T>`
4+
instead of `Task` or `Task<T>`. This is because Polly v8 was designed to be optimized
5+
for high performance and uses `ValueTask` to avoid unnecessary allocations.
6+
7+
One downside to this choice is that in Visual Basic and F#, it is not possible to directly
8+
await a method that returns `ValueTask` or `ValueTask<T>`, instead requiring the use of
9+
`Task` and `Task<T>`.
10+
11+
A proposal to support awaiting `ValueTask` can be found in F# language design repository:
12+
[[RFC FS-1021 Discussion] Support Interop with ValueTask in Async Type][fsharp-fslang-design-118].
13+
14+
To work around this limitation, you can use the [`AsTask()`][valuetask-astask] method to convert a
15+
`ValueTask` to a `Task` in F# and Visual Basic. This does however introduce an allocation and make
16+
the code a bit more difficult to work with compared to C#.
17+
18+
Examples of such conversions are shown below.
19+
20+
## F\#
21+
22+
```fsharp
23+
open FSharp.Control
24+
open System
25+
open System.Threading
26+
open System.Threading.Tasks
27+
open IcedTasks
28+
open Polly
29+
30+
let getBestFilmAsync token =
31+
task {
32+
do! Task.Delay(1000, token)
33+
return "https://www.imdb.com/title/tt0080684/"
34+
}
35+
36+
let demo () =
37+
task {
38+
// The ResiliencePipelineBuilder creates a ResiliencePipeline
39+
// that can be executed synchronously or asynchronously
40+
// and for both void and result-returning user-callbacks.
41+
let pipeline =
42+
ResiliencePipelineBuilder()
43+
.AddTimeout(TimeSpan.FromSeconds(5))
44+
.Build()
45+
46+
let token = CancellationToken.None
47+
48+
// Synchronously
49+
pipeline.Execute(fun () -> printfn "Hello, world!")
50+
51+
// Asynchronously
52+
// Note that Polly expects a ValueTask to be returned, so the function uses the valueTask builder
53+
// from IcedTasks to make it easier to use ValueTask. See https://github.com/TheAngryByrd/IcedTasks.
54+
do! pipeline.ExecuteAsync(
55+
fun token ->
56+
valueTask {
57+
printfn "Hello, world! Waiting for 2 seconds..."
58+
do! Task.Delay(1000, token)
59+
printfn "Wait complete."
60+
}
61+
, token
62+
)
63+
64+
// Synchronously with result
65+
let someResult = pipeline.Execute(fun token -> "some-result")
66+
67+
// Asynchronously with result
68+
// Note that Polly expects a ValueTask<T> to be returned, so the function uses the valueTask builder
69+
// from IcedTasks to make it easier to use ValueTask<T>. See https://github.com/TheAngryByrd/IcedTasks.
70+
let! bestFilm = pipeline.ExecuteAsync(
71+
fun token ->
72+
valueTask {
73+
let! url = getBestFilmAsync(token)
74+
return url
75+
}
76+
, token
77+
)
78+
79+
printfn $"Link to the best film: {bestFilm}"
80+
}
81+
```
82+
83+
[Source][sample-fsharp]
84+
85+
## Visual Basic
86+
87+
```vb
88+
Imports System.Threading
89+
Imports Polly
90+
91+
Module Program
92+
Sub Main()
93+
Demo().Wait()
94+
End Sub
95+
96+
Async Function Demo() As Task
97+
' The ResiliencePipelineBuilder creates a ResiliencePipeline
98+
' that can be executed synchronously or asynchronously
99+
' and for both void and result-returning user-callbacks.
100+
Dim pipeline = New ResiliencePipelineBuilder().AddTimeout(TimeSpan.FromSeconds(5)).Build()
101+
102+
' Synchronously
103+
pipeline.Execute(Sub()
104+
Console.WriteLine("Hello, world!")
105+
End Sub)
106+
107+
' Asynchronously
108+
' Note that the function is wrapped in a ValueTask for Polly to use as VB.NET cannot
109+
' await ValueTask directly, and AsTask() is used to convert the ValueTask returned by
110+
' ExecuteAsync() to a Task so it can be awaited.
111+
Await pipeline.ExecuteAsync(Function(token)
112+
Return New ValueTask(GreetAndWaitAsync(token))
113+
End Function,
114+
CancellationToken.None).AsTask()
115+
116+
' Synchronously with result
117+
Dim someResult = pipeline.Execute(Function(token)
118+
Return "some-result"
119+
End Function)
120+
121+
' Asynchronously with result
122+
' Note that the function is wrapped in a ValueTask(Of String) for Polly to use as VB.NET cannot
123+
' await ValueTask directly, and AsTask() is used to convert the ValueTask(Of String) returned by
124+
' ExecuteAsync() to a Task(Of String) so it can be awaited.
125+
Dim bestFilm = Await pipeline.ExecuteAsync(Function(token)
126+
Return New ValueTask(Of String)(GetBestFilmAsync(token))
127+
End Function,
128+
CancellationToken.None).AsTask()
129+
130+
Console.WriteLine("Link to the best film: {0}", bestFilm)
131+
132+
End Function
133+
134+
Async Function GreetAndWaitAsync(token As CancellationToken) As Task
135+
Console.WriteLine("Hello, world! Waiting for 1 second...")
136+
Await Task.Delay(1000, token)
137+
End Function
138+
139+
Async Function GetBestFilmAsync(token As CancellationToken) As Task(Of String)
140+
Await Task.Delay(1000, token)
141+
Return "https://www.imdb.com/title/tt0080684/"
142+
End Function
143+
End Module
144+
```
145+
146+
[Source][sample-vb]
147+
148+
[fsharp-fslang-design-118]: https://github.com/fsharp/fslang-design/discussions/118
149+
[valuetask-astask]: https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask.astask
150+
[sample-fsharp]: https://github.com/App-vNext/Polly/tree/main/samples/Intro.FSharp
151+
[sample-vb]: https://github.com/App-vNext/Polly/tree/main/samples/Intro.VisualBasic
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<Compile Include="Program.fs" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<PackageReference Include="FSharp.Core" />
16+
<PackageReference Include="IcedTasks" />
17+
<PackageReference Include="Polly.Core" />
18+
</ItemGroup>
19+
20+
</Project>

samples/Intro.FSharp/Program.fs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
open FSharp.Control
2+
open System
3+
open System.Threading
4+
open System.Threading.Tasks
5+
open IcedTasks
6+
open Polly
7+
8+
let getBestFilmAsync token =
9+
task {
10+
do! Task.Delay(1000, token)
11+
return "https://www.imdb.com/title/tt0080684/"
12+
}
13+
14+
let demo () =
15+
task {
16+
// The ResiliencePipelineBuilder creates a ResiliencePipeline
17+
// that can be executed synchronously or asynchronously
18+
// and for both void and result-returning user-callbacks.
19+
let pipeline =
20+
ResiliencePipelineBuilder()
21+
.AddTimeout(TimeSpan.FromSeconds(5))
22+
.Build()
23+
24+
let token = CancellationToken.None
25+
26+
// Synchronously
27+
pipeline.Execute(fun () -> printfn "Hello, world!")
28+
29+
// Asynchronously
30+
// Note that Polly expects a ValueTask to be returned, so the function uses the valueTask builder
31+
// from IcedTasks to make it easier to use ValueTask. See https://github.com/TheAngryByrd/IcedTasks.
32+
do! pipeline.ExecuteAsync(
33+
fun token ->
34+
valueTask {
35+
printfn "Hello, world! Waiting for 2 seconds..."
36+
do! Task.Delay(1000, token)
37+
printfn "Wait complete."
38+
}
39+
, token
40+
)
41+
42+
// Synchronously with result
43+
let someResult = pipeline.Execute(fun token -> "some-result")
44+
45+
// Asynchronously with result
46+
// Note that Polly expects a ValueTask<T> to be returned, so the function uses the valueTask builder
47+
// from IcedTasks to make it easier to use ValueTask<T>. See https://github.com/TheAngryByrd/IcedTasks.
48+
let! bestFilm = pipeline.ExecuteAsync(
49+
fun token ->
50+
valueTask {
51+
let! url = getBestFilmAsync(token)
52+
return url
53+
}
54+
, token
55+
)
56+
57+
printfn $"Link to the best film: {bestFilm}"
58+
}
59+
60+
[<EntryPoint>]
61+
let main _ =
62+
demo().Wait()
63+
0
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Polly.Core" />
12+
</ItemGroup>
13+
14+
</Project>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
Imports System.Threading
2+
Imports Polly
3+
4+
Module Program
5+
Sub Main()
6+
Demo().Wait()
7+
End Sub
8+
9+
Async Function Demo() As Task
10+
' The ResiliencePipelineBuilder creates a ResiliencePipeline
11+
' that can be executed synchronously or asynchronously
12+
' and for both void and result-returning user-callbacks.
13+
Dim pipeline = New ResiliencePipelineBuilder().AddTimeout(TimeSpan.FromSeconds(5)).Build()
14+
15+
' Synchronously
16+
pipeline.Execute(Sub()
17+
Console.WriteLine("Hello, world!")
18+
End Sub)
19+
20+
' Asynchronously
21+
' Note that the function is wrapped in a ValueTask for Polly to use as VB.NET cannot
22+
' await ValueTask directly, and AsTask() is used to convert the ValueTask returned by
23+
' ExecuteAsync() to a Task so it can be awaited.
24+
Await pipeline.ExecuteAsync(Function(token)
25+
Return New ValueTask(GreetAndWaitAsync(token))
26+
End Function,
27+
CancellationToken.None).AsTask()
28+
29+
' Synchronously with result
30+
Dim someResult = pipeline.Execute(Function(token)
31+
Return "some-result"
32+
End Function)
33+
34+
' Asynchronously with result
35+
' Note that the function is wrapped in a ValueTask(Of String) for Polly to use as VB.NET cannot
36+
' await ValueTask directly, and AsTask() is used to convert the ValueTask(Of String) returned by
37+
' ExecuteAsync() to a Task(Of String) so it can be awaited.
38+
Dim bestFilm = Await pipeline.ExecuteAsync(Function(token)
39+
Return New ValueTask(Of String)(GetBestFilmAsync(token))
40+
End Function,
41+
CancellationToken.None).AsTask()
42+
43+
Console.WriteLine("Link to the best film: {0}", bestFilm)
44+
45+
End Function
46+
47+
Async Function GreetAndWaitAsync(token As CancellationToken) As Task
48+
Console.WriteLine("Hello, world! Waiting for 1 second...")
49+
Await Task.Delay(1000, token)
50+
End Function
51+
52+
Async Function GetBestFilmAsync(token As CancellationToken) As Task(Of String)
53+
Await Task.Delay(1000, token)
54+
Return "https://www.imdb.com/title/tt0080684/"
55+
End Function
56+
End Module

samples/Samples.sln

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DependencyInjection", "Depe
2424
EndProject
2525
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Chaos", "Chaos\Chaos.csproj", "{A296E17C-B95F-4B15-8B0D-9D6CC0929A1D}"
2626
EndProject
27+
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Intro.VisualBasic", "Intro.VisualBasic\Intro.VisualBasic.vbproj", "{10F1C68E-DBF8-43DE-8A72-3EB4491ECD9C}"
28+
EndProject
29+
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Intro.FSharp", "Intro.FSharp\Intro.FSharp.fsproj", "{2C0F3F7F-63ED-472B-80B7-905618B07714}"
30+
EndProject
2731
Global
2832
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2933
Debug|Any CPU = Debug|Any CPU
@@ -54,6 +58,14 @@ Global
5458
{A296E17C-B95F-4B15-8B0D-9D6CC0929A1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
5559
{A296E17C-B95F-4B15-8B0D-9D6CC0929A1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
5660
{A296E17C-B95F-4B15-8B0D-9D6CC0929A1D}.Release|Any CPU.Build.0 = Release|Any CPU
61+
{10F1C68E-DBF8-43DE-8A72-3EB4491ECD9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
62+
{10F1C68E-DBF8-43DE-8A72-3EB4491ECD9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
63+
{10F1C68E-DBF8-43DE-8A72-3EB4491ECD9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
64+
{10F1C68E-DBF8-43DE-8A72-3EB4491ECD9C}.Release|Any CPU.Build.0 = Release|Any CPU
65+
{2C0F3F7F-63ED-472B-80B7-905618B07714}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
66+
{2C0F3F7F-63ED-472B-80B7-905618B07714}.Debug|Any CPU.Build.0 = Debug|Any CPU
67+
{2C0F3F7F-63ED-472B-80B7-905618B07714}.Release|Any CPU.ActiveCfg = Release|Any CPU
68+
{2C0F3F7F-63ED-472B-80B7-905618B07714}.Release|Any CPU.Build.0 = Release|Any CPU
5769
EndGlobalSection
5870
GlobalSection(SolutionProperties) = preSolution
5971
HideSolutionNode = FALSE

0 commit comments

Comments
 (0)