Reputation: 971
I am trying to figure out what I am doing wrong here. I'm sure it's something stupid, but I could use some extra eyes to figure out what it is. I'm trying to create a worker service that you can assign a work action delegate to. When I try to catch the exception thrown by the work action delegate the handler isn't invoked. I think it is because my action is actually an async method that returns Task and it isn't being awaited. But how am I supposed to know that the delegate is async? Why does C# let me assign a method that returns a Task to a action variable that is supposed to return void?
Any help would be appreciated!
[Fact]
public async Task CanCatchExceptions() {
var worker = new Worker {
WorkAction = async item => await Throws()
};
worker.DoWork(new WorkItem { Id = 1 });
}
public class Worker {
// would prefer to keep work action simple and not require a Task.
public Action<WorkItem> WorkAction { get; set; }
public void DoWork(WorkItem item) {
try {
WorkAction(item);
} catch {
Debug.WriteLine("Handled");
}
}
}
public class WorkItem {
public int Id { get; set; }
}
public async Task Throws() {
throw new ApplicationException();
}
Upvotes: 4
Views: 2635
Reputation: 149538
Why does C# let me assign a method that returns a Task to a action variable that is supposed to return void?
Because async void
is a legitimate use case for async event handlers, that's why the compiler permits the void returning Action<T>
and doesn't complain. Once you're aware of that fact, you can explicitly use a Func<Task>
which will create the desired Task
returning method overload.
As you say to you prefer to keep work action "simple" and not require a task, consider supplying an async overload which will behave properly with the async control flow:
public Func<WorkItem, Task> WorkAsync { get; set; }
public async Task WorkAsync(WorkItem item)
{
try
{
await WorkAsync(item)
}
catch (Exception e)
{
// Handle
}
}
Note this may be in a completely different class (seperate the async worker with the sync one), as per your choice.
Upvotes: 2
Reputation: 456587
Lambdas without return values may be cast to a task-returning method (e.g., Func<Task>
) or a void-returning method (e.g., Action
). Note that when casting to a void-returning method, the actual method for that lambda is an async void
method, with all the problems that come along with async void
methods. I describe some of the problems of async void
in my best practices article; one of them is that you can't catch exceptions with try
.
The best option is to change your work items to return Task
, which is a much more natural representation of asynchronous methods.
Upvotes: 5