question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Why and When use ExecuteAsync or Execute? [what happens when you execute an async delegate through a sync policy]

See original GitHub issue

Hi guys,

I already read your article explaining synchronous vs asynchronous policies, and I understand your point, but in the end, it looks like there is no difference if I use either ExecuteAsync or Execute method, in the below example I’m executing an async method with Sync and Async policies and it works ok in both cases, so what’s the difference, why I would use one or another, I’m confused with the behavior.

private async Task<bool> DoSomehitngAsync()
{
	await Task.Delay(TimeSpan.FromSeconds(1));
	return true;
}

public async Task TestAsync()
{
	var policy = Policy
		.Handle<Exception>()
		.RetryAsync(3);

	await policy.ExecuteAsync(async () =>
	{
		var something = await DoSomehitngAsync();
		Assert.AreEqual(true, something);
	});
}

public async void TestSync()
{
	var policy = Policy
		.Handle<Exception>()
		.Retry(3);

	await policy.Execute(async () =>
	{
		var something = await DoSomehitngAsync();
		Assert.AreEqual(true, something);
	});
}

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:3
  • Comments:6 (5 by maintainers)

github_iconTop GitHub Comments

12reactions
reisenbergercommented, Jul 13, 2018

To answer your original question:

Why and When use ExecuteAsync or Execute?

Use async policies and ExecuteAsync(…) any time you are executing an async delegate.

Why? In other words, why does TestSync() in the above example fail?

At the line marked // Providing the compiler an async delegate ... the code provides the compiler an async delegate where it expects a sync one. The compiler doesn’t complain. An async modifier isn’t actually intrinsically part of the delegate signature, it doesn’t cause a compile error due to method signature mismatch. All an async modifier does is say “the delegate can contain await statements”. And modifies the return signature of the delegate: in this case from void to Task. (An async modifier doesn’t even make a delegate run asynchronously, it just (broadly) does those two previously mentioned things.) For more background on these nuances (if unfamiliar) check out articles/blogs by Stephen Cleary and Stephen Toub. So your whole executed async () => delegate here is - as far as the compiler is concerned - just a Func<Task>.

So at the line marked policy.Execute( // Executing a Func<Task> delegate through a sync policy, the code is actually doing policy.Execute<Task>(Func<Task> foo). It’s saying: execute that delegate through the policy and give me back the Task the delegate returns.

The final piece of understanding relies on knowing what await actually does. When execution hits an await statement, it immediately returns from the method synchronously, returning a Task representing the ongoing work. So what happens when you await policy.Execute<Task>(Func<Task> foo) is this:

  • policy executes the Func<Task> that is your delegate
  • as soon as execution hits the first await, in var something = await DoSomethingAsync();, the delegate synchronously (see above) returns the Task representing the rest of the execution.
  • no exception has been thrown up to this point, the delegate has successfully returned a Task, so as far as policy is concerned, the execution has completed with success. policy has finished its work (it executed a synchronous delegate and that delegate returned synchronously without an exception yet), so policy returns that Task to the calling code and plays no further part in the execution.
  • back in your calling code, you then await that returned Task.
  • one second later that Task throws. The calling code is awaiting a Task that throws, so it ~bombs out~ rethrows.

Does that help?


EDIT: To look at this another way, we can look at why it works with the async policy and not with the sync policy.

  • If we look at the internals of the async policy, we see it is (as you would expect) executing the user delegate with await. So it awaits the Task that represents doing the user-delegate work (after the first await is hit). So it captures the exception thrown at if (++counter <= 2) throw new Exception(...);. So the rest of the policy can handle that exception.
  • If we look at the internals of the sync policy, it is (as you would expect) executing the user delegate without await. So (when an async delegate is executed through a sync policy) it immediately returns the Task which represents doing the user-delegate work (after the first await is hit). So it doesn’t govern the exception thrown at if (++counter <= 2) ...

In general the moral of the story is: don’t mix sync and async code - particularly where exception-handling is involved.

10reactions
reisenbergercommented, Jul 13, 2018

@vany0114 You are seeing no difference in the specific example you posted because the delegates executed through the policy do not fault. Try this code and you’ll see a difference. The key difference is that the executed delegate faults (but few enough times that the policy should handle it). Incidental (not significant) differences are:

  • I changed from Assert.Fail(...) only to quickly run this up as a console app.
  • I changed the signature of TestSync() to async Task so that we could await it properly. (Won’t go into a side discussion here about the perils of async void which you may know anyway.)

I’ll post a follow-up post in a moment explaining why TestSync() fails (but post back if it’s immediately self-evident to you, on seeing this!)

    class Program
    {
        static async Task Main(string[] args)
        {
            var toTestSync = new PollySyncAsyncTest();
            var toTestAsync = new PollySyncAsyncTest();

            await toTestSync.TestSync();
            await toTestAsync.TestAsync();
        }
    }

    class PollySyncAsyncTest
    {
        private int counter = 0;

        public async Task TestAsync()
        {
            var policy = Policy
                .Handle<Exception>()
                .RetryAsync(3);

            await policy.ExecuteAsync(async () =>
            {
                var something = await DoSomethingAsync();
                if (something != true) throw new Exception("Assert fail");
            });
        }

        public async Task TestSync()
        {
            var policy = Policy
                .Handle<Exception>()
                .Retry(3);

            await policy.Execute( // Executing a Func<Task> delegate through a sync policy.
                async () => // Providing the compiler an async delegate where it expects a sync delegate.
            {
                var something = await DoSomethingAsync();
                if (something != true) throw new Exception("Assert fail");
            });
        }

        private async Task<bool> DoSomethingAsync()
        {
            await Task.Delay(TimeSpan.FromSeconds(1));

            if (++counter <= 2) throw new Exception("Fault which we might expect the policy to handle");

            return true;
        }
    }
Read more comments on GitHub >

github_iconTop Results From Across the Web

Use ExecuteAsync to execute messages asynchronously
You configure the request and pass the request instance as an argument to IOrganizationService.Execute. ExecuteAsyncResponse returns with the ID ...
Read more >
Cleanly formalise the separation of sync and async policies ...
You can only Execute() (etc) on synchronous policies; ExecuteAsync() on async policies; but not use one policy instance for both sync and async...
Read more >
What is the correct way to call Polly ExecuteAsync method?
1 Answer 1 ... In this simple case, there's no semantic difference. The version eliding async and await has an almost-immeasurable performance ...
Read more >
execute() vs executeAsync() in Script Action
As I understand it, executeAsync is asynchronous, but waitForResponse forces it to act synchronously, and if the timer runs out for either ...
Read more >
Using Execution Context in Polly
For both sync and async executions, Polly ensures that policy hooks (eg onRetry or onBreak delegates) complete before the next phase of policy ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found