Per the answer to this question, the form for capturing an exception thrown by an asynchronous method looks like this:
public async void DoFoo()
{
    try
    {
        await Foo();
    }
    catch (ProtocolException ex)
    {
          /* The exception will be caught because you've awaited the call. */
    }
}
Great. This seems to disintegrate if I want to bubble up several levels of asynchrony though. Here's where the exception originates:
internal static async Task MakePdfPagesFromPdf(Pdf pdf, byte[] pdfBytes, int jobId)
{
    IEnumerable<Image> pdfAsImages = PdfOperations.PdfToImagesPdfium(pdfBytes, dpi);
    if(pdfAsImages.Count() < 1)
    {
        throw new ArgumentException("PDF has no pages.");
    }
    // ... more code ...
}
Here's the method that calls MakePdfPagesFromPdf:
internal static async Task ProcessBase64Pdf(Pdf pdf, int jobId, string componentDesignName)
{
    byte[] pdfBytes = ConvertBase64ToPdfByteArray(pdf.Url); // Base64 data is in pdf.Url
    await UploadPdfToAwsS3(pdf, pdfBytes, jobId, componentDesignName);
    try
    {
        await MakePdfPagesFromPdf(pdf, pdfBytes, jobId);
    }
    catch(ArgumentException argumentException)
    {
        throw argumentException;
    }
}
I catch the exception like in the example cited at the beginning of this question. Debugging asserts that this catch block is hit. However, I need to bubble the exception up one more level, to inside a controller route:
try
{
    await PdfsController.ProcessBase64Pdf(pdf, componentDesign.JobId, componentDesign.Name);
}
catch (ArgumentException argumentException)
{
    // Now do stuff with the exception                  
}
It doesn't hit this highest level catch at a breakpoint. Removing the intermediate catch has no effect. The route continues and returns, but I am not able to hit breakpoints after the ArgumentException is thrown from the intermediate catch. What's going on here and how can I hit breakpoints through this whole asynchronous stack?
 
     
     
    