From f8a8a32c3b24e883ff28f5cafbf3f788d4fbe526 Mon Sep 17 00:00:00 2001 From: martincostello Date: Tue, 9 Apr 2024 14:02:12 +0100 Subject: [PATCH 1/6] Add F# and VB.NET samples Relates to #2045. --- Directory.Packages.props | 1 + samples/Intro.FSharp/Intro.FSharp.fsproj | 19 +++++++ samples/Intro.FSharp/Program.fs | 49 ++++++++++++++++ .../Intro.VisualBasic.vbproj | 14 +++++ samples/Intro.VisualBasic/Program.vb | 56 +++++++++++++++++++ samples/Samples.sln | 12 ++++ 6 files changed, 151 insertions(+) create mode 100644 samples/Intro.FSharp/Intro.FSharp.fsproj create mode 100644 samples/Intro.FSharp/Program.fs create mode 100644 samples/Intro.VisualBasic/Intro.VisualBasic.vbproj create mode 100644 samples/Intro.VisualBasic/Program.vb diff --git a/Directory.Packages.props b/Directory.Packages.props index f886355373b..929acce4252 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -8,6 +8,7 @@ + diff --git a/samples/Intro.FSharp/Intro.FSharp.fsproj b/samples/Intro.FSharp/Intro.FSharp.fsproj new file mode 100644 index 00000000000..a3e58bdcbd7 --- /dev/null +++ b/samples/Intro.FSharp/Intro.FSharp.fsproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/samples/Intro.FSharp/Program.fs b/samples/Intro.FSharp/Program.fs new file mode 100644 index 00000000000..bbd2c76503b --- /dev/null +++ b/samples/Intro.FSharp/Program.fs @@ -0,0 +1,49 @@ +open System +open System.Threading +open System.Threading.Tasks +open Polly + +let getBestFilmAsync(token: CancellationToken) = + task { + do! Task.Delay(1000, token) |> Async.AwaitTask + return "https://www.imdb.com/title/tt0080684/" + } + +let demo() = + async { + // 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() + + // Synchronously + pipeline.Execute(Action(fun () -> Console.WriteLine("Hello, world!"))) + + // Asynchronously + // Note that the function is wrapped in a ValueTask for Polly to use as F# cannot + // await ValueTask directly, and AsTask() is used to convert the ValueTask returned by + // ExecuteAsync() to a Task so it can be awaited. + do! pipeline.ExecuteAsync(Func(fun token -> new ValueTask(task { + Console.WriteLine("Hello, world! Waiting for 1 second...") + do! Task.Delay(1000, token) |> Async.AwaitTask + })), + CancellationToken.None).AsTask() |> Async.AwaitTask + + // Synchronously with result + let someResult = pipeline.Execute(Func(fun token -> "some-result")) + + // Asynchronously with result + // Note that the function is wrapped in a ValueTask for Polly to use as F# cannot + // await ValueTask directly, and AsTask() is used to convert the ValueTask returned by + // ExecuteAsync() to a Task so it can be awaited. + let! bestFilm = pipeline.ExecuteAsync( + Func>(fun token -> new ValueTask(getBestFilmAsync(token))), + CancellationToken.None).AsTask() |> Async.AwaitTask + + Console.WriteLine("Link to the best film: {0}", bestFilm) + } + +[] +let main _ = + demo() |> Async.RunSynchronously + 0 diff --git a/samples/Intro.VisualBasic/Intro.VisualBasic.vbproj b/samples/Intro.VisualBasic/Intro.VisualBasic.vbproj new file mode 100644 index 00000000000..499481d3d4e --- /dev/null +++ b/samples/Intro.VisualBasic/Intro.VisualBasic.vbproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/samples/Intro.VisualBasic/Program.vb b/samples/Intro.VisualBasic/Program.vb new file mode 100644 index 00000000000..de22f08b711 --- /dev/null +++ b/samples/Intro.VisualBasic/Program.vb @@ -0,0 +1,56 @@ +Imports System.Threading +Imports Polly + +Module Progam + 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 diff --git a/samples/Samples.sln b/samples/Samples.sln index ade0ec3fc68..c1e66935c3e 100644 --- a/samples/Samples.sln +++ b/samples/Samples.sln @@ -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 @@ -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 From 70c71d932436c6a911f494b90bb621d62745e90c Mon Sep 17 00:00:00 2001 From: martincostello Date: Tue, 9 Apr 2024 16:28:23 +0100 Subject: [PATCH 2/6] Add F# and VB docs Add documentation for using Polly with F# and VB. --- docs/getting-started.md | 4 + docs/toc.yml | 3 + docs/use-with-fsharp-and-visual-basic.md | 137 +++++++++++++++++++++++ samples/Intro.VisualBasic/Program.vb | 2 +- 4 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 docs/use-with-fsharp-and-visual-basic.md diff --git a/docs/getting-started.md b/docs/getting-started.md index 9d434d5e7e6..f4ae874c950 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -23,6 +23,10 @@ await pipeline.ExecuteAsync(static async token => { /* Your custom logic goes he ``` +> [!NOTE] +> Asynchronous methods in the Polly API return `ValueTask` or `ValueTask` instead of `Task` or `Task`. +> 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: diff --git a/docs/toc.yml b/docs/toc.yml index 6f0f230254e..886d7404fc0 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -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: diff --git a/docs/use-with-fsharp-and-visual-basic.md b/docs/use-with-fsharp-and-visual-basic.md new file mode 100644 index 00000000000..885b21e53ea --- /dev/null +++ b/docs/use-with-fsharp-and-visual-basic.md @@ -0,0 +1,137 @@ +# Use with F# and Visual Basic + +Asynchronous methods in the Polly.Core API return either `ValueTask` or `ValueTask` +instead of `Task` or `Task`. 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`, instead requiring the use of +`Task` and `Task`. + +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 System +open System.Threading +open System.Threading.Tasks +open Polly + +let getBestFilmAsync(token: CancellationToken) = + task { + do! Task.Delay(1000, token) |> Async.AwaitTask + return "https://www.imdb.com/title/tt0080684/" + } + +let demo() = + async { + // 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() + + // Synchronously + pipeline.Execute(Action(fun () -> Console.WriteLine("Hello, world!"))) + + // Asynchronously + // Note that the function is wrapped in a ValueTask for Polly to use as F# cannot + // await ValueTask directly, and AsTask() is used to convert the ValueTask returned by + // ExecuteAsync() to a Task so it can be awaited. + do! pipeline.ExecuteAsync(Func(fun token -> new ValueTask(task { + Console.WriteLine("Hello, world! Waiting for 1 second...") + do! Task.Delay(1000, token) |> Async.AwaitTask + })), + CancellationToken.None).AsTask() |> Async.AwaitTask + + // Synchronously with result + let someResult = pipeline.Execute(Func(fun token -> "some-result")) + + // Asynchronously with result + // Note that the function is wrapped in a ValueTask for Polly to use as F# cannot + // await ValueTask directly, and AsTask() is used to convert the ValueTask returned by + // ExecuteAsync() to a Task so it can be awaited. + let! bestFilm = pipeline.ExecuteAsync( + Func>(fun token -> new ValueTask(getBestFilmAsync(token))), + CancellationToken.None).AsTask() |> Async.AwaitTask + + Console.WriteLine("Link to the best film: {0}", 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 diff --git a/samples/Intro.VisualBasic/Program.vb b/samples/Intro.VisualBasic/Program.vb index de22f08b711..dac80f4d217 100644 --- a/samples/Intro.VisualBasic/Program.vb +++ b/samples/Intro.VisualBasic/Program.vb @@ -1,7 +1,7 @@ Imports System.Threading Imports Polly -Module Progam +Module Program Sub Main() Demo().Wait() End Sub From 3e1ff7b5576cac02b742417db4992704bea9f4a7 Mon Sep 17 00:00:00 2001 From: martincostello Date: Tue, 9 Apr 2024 16:34:30 +0100 Subject: [PATCH 3/6] Update wordlist Add some additional terminology. --- .github/wordlist.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/wordlist.txt b/.github/wordlist.txt index 0ddf7ccf1ce..3a38808e63a 100644 --- a/.github/wordlist.txt +++ b/.github/wordlist.txt @@ -1,6 +1,7 @@ alloc apis ASP.NET +astask async azurefunctions bcl @@ -18,7 +19,9 @@ enricher eshoponcontainers extensibility flurl +fs hangfire +interop jetbrains jitter jittered @@ -67,6 +70,7 @@ timingpolicy ui unhandled uwp +valuetask waitandretry wpf xunit From e1018cf20d199ed2b439da390f3d3b46a17c2f94 Mon Sep 17 00:00:00 2001 From: martincostello Date: Tue, 9 Apr 2024 17:40:30 +0100 Subject: [PATCH 4/6] Make F# a little more idiomatic Make the sample for using Polly from F# more idiomatic for those users. Co-Authored-By: Stuart Lang --- docs/use-with-fsharp-and-visual-basic.md | 45 +++++++++++---------- samples/Intro.FSharp/Program.fs | 51 ++++++++++++------------ 2 files changed, 49 insertions(+), 47 deletions(-) diff --git a/docs/use-with-fsharp-and-visual-basic.md b/docs/use-with-fsharp-and-visual-basic.md index 885b21e53ea..3acc429426a 100644 --- a/docs/use-with-fsharp-and-visual-basic.md +++ b/docs/use-with-fsharp-and-visual-basic.md @@ -20,49 +20,50 @@ Examples of such conversions are shown below. ## F\# ```fsharp +open FSharp.Control open System open System.Threading open System.Threading.Tasks open Polly -let getBestFilmAsync(token: CancellationToken) = +let getBestFilmAsync token = task { - do! Task.Delay(1000, token) |> Async.AwaitTask + do! Task.Delay(1000, token) return "https://www.imdb.com/title/tt0080684/" } -let demo() = - async { +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 pipeline = + ResiliencePipelineBuilder().AddTimeout(TimeSpan.FromSeconds(5)).Build() // Synchronously - pipeline.Execute(Action(fun () -> Console.WriteLine("Hello, world!"))) + pipeline.Execute(fun () -> printfn "Hello, world!") // Asynchronously - // Note that the function is wrapped in a ValueTask for Polly to use as F# cannot - // await ValueTask directly, and AsTask() is used to convert the ValueTask returned by - // ExecuteAsync() to a Task so it can be awaited. - do! pipeline.ExecuteAsync(Func(fun token -> new ValueTask(task { - Console.WriteLine("Hello, world! Waiting for 1 second...") - do! Task.Delay(1000, token) |> Async.AwaitTask - })), - CancellationToken.None).AsTask() |> Async.AwaitTask + // Note that Polly expects a ValueTask to be returned, so the function is piped into a ValueTask constructor. + do! + pipeline.ExecuteAsync( + fun token -> + task { + printfn "Hello, world! Waiting for 1 second..." + do! Task.Delay(1000, token) + } |> ValueTask + , CancellationToken.None + ) // Synchronously with result - let someResult = pipeline.Execute(Func(fun token -> "some-result")) + let someResult = pipeline.Execute(fun token -> "some-result") // Asynchronously with result - // Note that the function is wrapped in a ValueTask for Polly to use as F# cannot - // await ValueTask directly, and AsTask() is used to convert the ValueTask returned by - // ExecuteAsync() to a Task so it can be awaited. - let! bestFilm = pipeline.ExecuteAsync( - Func>(fun token -> new ValueTask(getBestFilmAsync(token))), - CancellationToken.None).AsTask() |> Async.AwaitTask + // Note that Polly expects a ValueTask to be returned, so the function is piped into a ValueTask constructor. + let! bestFilm = + pipeline.ExecuteAsync((fun token -> getBestFilmAsync(token) |> ValueTask), CancellationToken.None) - Console.WriteLine("Link to the best film: {0}", bestFilm) + printfn $"Link to the best film: {bestFilm}" } ``` diff --git a/samples/Intro.FSharp/Program.fs b/samples/Intro.FSharp/Program.fs index bbd2c76503b..b0bf384020e 100644 --- a/samples/Intro.FSharp/Program.fs +++ b/samples/Intro.FSharp/Program.fs @@ -1,49 +1,50 @@ -open System +open FSharp.Control +open System open System.Threading open System.Threading.Tasks open Polly -let getBestFilmAsync(token: CancellationToken) = +let getBestFilmAsync token = task { - do! Task.Delay(1000, token) |> Async.AwaitTask + do! Task.Delay(1000, token) return "https://www.imdb.com/title/tt0080684/" } -let demo() = - async { +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 pipeline = + ResiliencePipelineBuilder().AddTimeout(TimeSpan.FromSeconds(5)).Build() // Synchronously - pipeline.Execute(Action(fun () -> Console.WriteLine("Hello, world!"))) + pipeline.Execute(fun () -> printfn "Hello, world!") // Asynchronously - // Note that the function is wrapped in a ValueTask for Polly to use as F# cannot - // await ValueTask directly, and AsTask() is used to convert the ValueTask returned by - // ExecuteAsync() to a Task so it can be awaited. - do! pipeline.ExecuteAsync(Func(fun token -> new ValueTask(task { - Console.WriteLine("Hello, world! Waiting for 1 second...") - do! Task.Delay(1000, token) |> Async.AwaitTask - })), - CancellationToken.None).AsTask() |> Async.AwaitTask + // Note that Polly expects a ValueTask to be returned, so the function is piped into a ValueTask constructor. + do! + pipeline.ExecuteAsync( + fun token -> + task { + printfn "Hello, world! Waiting for 1 second..." + do! Task.Delay(1000, token) + } |> ValueTask + , CancellationToken.None + ) // Synchronously with result - let someResult = pipeline.Execute(Func(fun token -> "some-result")) + let someResult = pipeline.Execute(fun token -> "some-result") // Asynchronously with result - // Note that the function is wrapped in a ValueTask for Polly to use as F# cannot - // await ValueTask directly, and AsTask() is used to convert the ValueTask returned by - // ExecuteAsync() to a Task so it can be awaited. - let! bestFilm = pipeline.ExecuteAsync( - Func>(fun token -> new ValueTask(getBestFilmAsync(token))), - CancellationToken.None).AsTask() |> Async.AwaitTask - - Console.WriteLine("Link to the best film: {0}", bestFilm) + // Note that Polly expects a ValueTask to be returned, so the function is piped into a ValueTask constructor. + let! bestFilm = + pipeline.ExecuteAsync((fun token -> getBestFilmAsync(token) |> ValueTask), CancellationToken.None) + + printfn $"Link to the best film: {bestFilm}" } [] let main _ = - demo() |> Async.RunSynchronously + demo().Wait() 0 From d62456bc50be706a56f1474fe175a73cff7aeea6 Mon Sep 17 00:00:00 2001 From: martincostello Date: Thu, 18 Apr 2024 10:27:18 +0100 Subject: [PATCH 5/6] Use IcedTasks with F# sample Apply feedback to use IcedTasks to consume ValueTask in the C# sample. Co-Authored-By: Chet Husk <573979+baronfel@users.noreply.github.com> --- Directory.Packages.props | 1 + docs/use-with-fsharp-and-visual-basic.md | 40 +++++++++++++++++------- samples/Intro.FSharp/Intro.FSharp.fsproj | 1 + samples/Intro.FSharp/Program.fs | 40 +++++++++++++++++------- 4 files changed, 58 insertions(+), 24 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 929acce4252..2a3262ca1e4 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -10,6 +10,7 @@ + diff --git a/docs/use-with-fsharp-and-visual-basic.md b/docs/use-with-fsharp-and-visual-basic.md index 3acc429426a..d7844576a72 100644 --- a/docs/use-with-fsharp-and-visual-basic.md +++ b/docs/use-with-fsharp-and-visual-basic.md @@ -24,6 +24,7 @@ open FSharp.Control open System open System.Threading open System.Threading.Tasks +open IcedTasks open Polly let getBestFilmAsync token = @@ -38,30 +39,45 @@ let demo () = // that can be executed synchronously or asynchronously // and for both void and result-returning user-callbacks. let pipeline = - ResiliencePipelineBuilder().AddTimeout(TimeSpan.FromSeconds(5)).Build() + 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 is piped into a ValueTask constructor. + // 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 -> - task { - printfn "Hello, world! Waiting for 1 second..." - do! Task.Delay(1000, token) - } |> ValueTask - , CancellationToken.None - ) + valueTask { + 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 to be returned, so the function is piped into a ValueTask constructor. + // 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. let! bestFilm = - pipeline.ExecuteAsync((fun token -> getBestFilmAsync(token) |> ValueTask), CancellationToken.None) + valueTask { + let! url = pipeline.ExecuteAsync((fun token -> valueTask { + let! url = getBestFilmAsync(token) + return url + }), token) + return url + } printfn $"Link to the best film: {bestFilm}" } diff --git a/samples/Intro.FSharp/Intro.FSharp.fsproj b/samples/Intro.FSharp/Intro.FSharp.fsproj index a3e58bdcbd7..414975d92eb 100644 --- a/samples/Intro.FSharp/Intro.FSharp.fsproj +++ b/samples/Intro.FSharp/Intro.FSharp.fsproj @@ -13,6 +13,7 @@ + diff --git a/samples/Intro.FSharp/Program.fs b/samples/Intro.FSharp/Program.fs index b0bf384020e..df0258cf06b 100644 --- a/samples/Intro.FSharp/Program.fs +++ b/samples/Intro.FSharp/Program.fs @@ -2,6 +2,7 @@ open System open System.Threading open System.Threading.Tasks +open IcedTasks open Polly let getBestFilmAsync token = @@ -16,30 +17,45 @@ let demo () = // that can be executed synchronously or asynchronously // and for both void and result-returning user-callbacks. let pipeline = - ResiliencePipelineBuilder().AddTimeout(TimeSpan.FromSeconds(5)).Build() + 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 is piped into a ValueTask constructor. + // 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 -> - task { - printfn "Hello, world! Waiting for 1 second..." - do! Task.Delay(1000, token) - } |> ValueTask - , CancellationToken.None - ) + valueTask { + 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 to be returned, so the function is piped into a ValueTask constructor. + // 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. let! bestFilm = - pipeline.ExecuteAsync((fun token -> getBestFilmAsync(token) |> ValueTask), CancellationToken.None) + valueTask { + let! url = pipeline.ExecuteAsync((fun token -> valueTask { + let! url = getBestFilmAsync(token) + return url + }), token) + return url + } printfn $"Link to the best film: {bestFilm}" } From fbbc057a921e0036ba0e226518fd8a7ca51feaad Mon Sep 17 00:00:00 2001 From: martincostello Date: Thu, 18 Apr 2024 15:26:30 +0100 Subject: [PATCH 6/6] Remove extra wrappers Remove `valueTask` wrappers that are redundant. Co-Authored-By: Jimmy Byrd <1490044+theangrybyrd@users.noreply.github.com> --- docs/use-with-fsharp-and-visual-basic.md | 33 +++++++++++------------- samples/Intro.FSharp/Program.fs | 33 +++++++++++------------- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/docs/use-with-fsharp-and-visual-basic.md b/docs/use-with-fsharp-and-visual-basic.md index d7844576a72..1100c3c16d9 100644 --- a/docs/use-with-fsharp-and-visual-basic.md +++ b/docs/use-with-fsharp-and-visual-basic.md @@ -51,18 +51,15 @@ let demo () = // 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! - valueTask { - do! pipeline.ExecuteAsync( - fun token -> - valueTask { - printfn "Hello, world! Waiting for 2 seconds..." - do! Task.Delay(1000, token) - printfn "Wait complete." - } - , token - ) - } + 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") @@ -70,14 +67,14 @@ let demo () = // Asynchronously with result // 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. - let! bestFilm = - valueTask { - let! url = pipeline.ExecuteAsync((fun token -> valueTask { + let! bestFilm = pipeline.ExecuteAsync( + fun token -> + valueTask { let! url = getBestFilmAsync(token) return url - }), token) - return url - } + } + , token + ) printfn $"Link to the best film: {bestFilm}" } diff --git a/samples/Intro.FSharp/Program.fs b/samples/Intro.FSharp/Program.fs index df0258cf06b..f7b7ca02216 100644 --- a/samples/Intro.FSharp/Program.fs +++ b/samples/Intro.FSharp/Program.fs @@ -29,18 +29,15 @@ let demo () = // 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! - valueTask { - do! pipeline.ExecuteAsync( - fun token -> - valueTask { - printfn "Hello, world! Waiting for 2 seconds..." - do! Task.Delay(1000, token) - printfn "Wait complete." - } - , token - ) - } + 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") @@ -48,14 +45,14 @@ let demo () = // Asynchronously with result // 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. - let! bestFilm = - valueTask { - let! url = pipeline.ExecuteAsync((fun token -> valueTask { + let! bestFilm = pipeline.ExecuteAsync( + fun token -> + valueTask { let! url = getBestFilmAsync(token) return url - }), token) - return url - } + } + , token + ) printfn $"Link to the best film: {bestFilm}" }