使用异步委托进行异常处理

4
我在尝试找出我的错误,但是需要额外的帮助。我想创建一个工作服务,可以将工作操作委托分配给它。当我尝试捕获由工作操作委托抛出的异常时,处理程序没有被调用。我认为这是因为我的操作实际上是一个返回Task的异步方法,并且它没有被等待。但是我怎么知道委托是异步的呢?为什么C#允许我将返回Task的方法分配给应该返回void的action变量?如果您能提供任何帮助,我将不胜感激!
[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();
}

您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Eric J. Smith
我认为Stephen的回答已经解决了这个问题。但是再强调一下:该语言有特殊规则允许这样做。它会隐式地将async Task等同于async void,这就是在您的情况下发生的事情,从而允许使用lambda表达式作为Action<WorkItem>委托类型。但请注意,此转换不会更改async方法的行为;它仍然在第一个await语句处返回,因此除非等待(如果是void,则无法等待),否则调用者可以(通常)在async方法之前完成。 - Peter Duniho
2个回答

4
没有返回值的Lambda表达式可以转换为返回任务的方法(例如Func<Task>)或返回void的方法(例如Action)。请注意,当转换为返回void的方法时,该Lambda表达式的实际方法是一个async void方法,具有所有与async void方法相关的问题。我在我的最佳实践文章中描述了一些async void的问题;其中之一是您无法使用try捕获异常。
最佳选择是将工作项更改为返回Task,这是异步方法更自然的表示形式。

2
为什么C#允许我将返回Task的方法分配给应该返回void的action变量?因为async void是异步事件处理程序的合法用例,编译器允许无返回值的Action<T>而不会抱怨。一旦你意识到这一点,你可以明确使用Func<Task>来创建所需的返回Task的方法重载。如果您希望保持工作的"简单"并且不需要一个任务,请考虑提供一个异步重载,它将与异步控制流正确地交互。
public Func<WorkItem, Task> WorkAsync { get; set; }

public async Task WorkAsync(WorkItem item)
{
      try
      {
            await WorkAsync(item)
      }
      catch (Exception e)
      {
           // Handle
      }
}

请注意,这可能是完全不同的类(将异步工作者与同步工作者分开),根据您的选择。

1
这就是我最终做的。 - Eric J. Smith

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接