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);
});
}
@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:
Assert.Fail(...) only to quickly run this up as a console app. 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;
}
}
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 delegateawait, in var something = await DoSomethingAsync();, the delegate synchronously (see above) returns the Task representing the rest of the execution.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.await that returned Task. 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.
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.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.
@reisenberger thanks for the great explanation! I just wanted to be aware in order to use them properly.
@vany0114 np. Thanks for the great question!
I had the same question and this is a great explanation @reisenberger. Should definitely be added to the wiki!
Most helpful comment
To answer your original question:
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 anasyncdelegate where it expects a sync one. The compiler doesn't complain. Anasyncmodifier isn't actually intrinsically part of the delegate signature, it doesn't cause a compile error due to method signature mismatch. All anasyncmodifier does is say "the delegate can containawaitstatements". And modifies the return signature of the delegate: in this case fromvoidtoTask. (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 executedasync () =>delegate here is - as far as the compiler is concerned - just aFunc<Task>.So at the line marked
policy.Execute( // Executing a Func<Task> delegate through a sync policy, the code is actually doingpolicy.Execute<Task>(Func<Task> foo). It's saying: execute that delegate through the policy and give me back theTaskthe delegate returns.The final piece of understanding relies on knowing what
awaitactually does. When execution hits anawaitstatement, it _immediately_ returns from the method _synchronously_, returning aTaskrepresenting the ongoing work. So what happens when youawait policy.Execute<Task>(Func<Task> foo)is this:policyexecutes theFunc<Task>that is your delegateawait, invar something = await DoSomethingAsync();, the delegate synchronously (see above) returns theTaskrepresenting the rest of the execution.Task, so as far aspolicyis concerned, the execution has completed with success.policyhas finished its work (it executed a synchronous delegate and that delegate returned synchronously without an exception yet), sopolicyreturns thatTaskto the calling code and plays no further part in the execution.awaitthat returnedTask.Taskthrows. 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.
await. So it awaits theTaskthat represents doing the user-delegate work (after the first await is hit). So it captures the exception thrown atif (++counter <= 2) throw new Exception(...);. So the rest of the policy can handle that exception.await. So (when an async delegate is executed through a sync policy) it immediately returns theTaskwhich represents doing the user-delegate work (after the first await is hit). So it doesn't govern the exception thrown atif (++counter <= 2) ...In general the moral of the story is: don't mix sync and
asynccode - particularly where exception-handling is involved.