当使用async/await和Task.Run时,不会显示特定的异常

本文关键字:显示 异常 Run async await Task | 更新日期: 2023-09-27 18:02:08

我有一个食谱列表的网页。在我的代码中,我循环遍历页面并下载每个食谱。下面是我正在做的伪代码:

//This is the Recipe class
//The constructor accepts a link to the recipe
//This method scrapes the recipe
public Task ScrapeAsync(){
    return Task.Run(async () => {
        string WebPage;
        WebRequest request = WebRequest.Create(link);
        request.Method = "GET";
        using (WebResponse response = await request.GetResponseAsync())
        using (StreamReader reader = new StreamReader(response.GetResponseStream())) {
            WebPage = await reader.ReadToEndAsync();
        }
        //Non - async code here which is used to scrape the webpage
    }
}

我使用Task。因为方法中既有异步代码,也有阻塞代码。

//This is the RecipePage class
//This method scrapes the page
public Task GetRecipeListAsync(){
    return Task.Run(async () => {
        //I get the page using WebRequest and WebResponse in the same way as above
        //Non - async/blocking code here which scrapes the page and finds links to recipes. I do await Recipe.ScrapeAsync() somewhere here.
        //And I add the recipe objects to a public list in this class
    }
}

在表单中,它循环遍历页面列表并执行await RecipePage.GetRecipeList()和其他操作。
这里是for循环的位置:

private async void go_Click(object sender, EventArgs e){
    for (int i = (int)startingPageNUD.Value; i <= (int)finishingPageNUD.Value; ++i) {
        RecipePage page = new RecipePage(page + i);
        await page.GetRecipeListAsync();
        //Some other code here
    }
}

我的问题是,每当一个异常发生在ScrapeAsync方法,Visual Studio 2013指向Application.Run(new Form1())

static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }

并告诉我Reflection.TargetInvocationException已经发生。它没有显示我的代码中的实际异常。例如,如果我在代码中得到NullReferenceException,它不会显示这一点。正因为如此,我不得不编写异步和非异步代码,并使用非异步代码进行调试。有办法解决这个问题吗?
我还有一个问题。我是否以正确的方式使用async/await和Task ?

当使用async/await和Task.Run时,不会显示特定的异常

我是否以正确的方式使用async/await和Task ?

相当接近。我认为在这种情况下不需要Task.Run(它应该只用于将cpu密集型操作移出UI线程,并且HTML抓取通常足够快,可以留在UI上而不会产生任何不利影响)。

我的另一个建议是让async Task方法尽可能返回值,而不是修改成员变量作为副作用。在您的示例中,请考虑让ScrapeAsync返回它自己的列表,而不是更新共享列表,并让调用代码进行列表合并。

关于您的异常,TargetInvocationException将具有InnerException的详细信息。async void事件处理程序中的异常与常规void事件处理程序中的异常的管理方式几乎相同:如果一个人转义事件处理程序,那么它将进入应用程序主循环。如果你不想这样,你必须捕获它(对于同步和异步处理程序)。

好的。感谢你的编辑,我现在可以回答了。

你的问题是这个函数:

private async void go_Click(object sender, EventArgs e){
    for (int i = (int)startingPageNUD.Value; i <= (int)finishingPageNUD.Value; ++i) {
        RecipePage page = new RecipePage(page + i);
        await page.GetRecipeListAsync();
        //Some other code here
    }
}

问题是,函数一旦到达await,就返回到调用它的对象。现在,如果page.GetRecipeListAsync();抛出异常,这个异常将在延续处理程序中抛出。此处理程序在UI的任务队列中执行。在那里抛出的异常会导致UI的任务循环崩溃,这会产生各种奇怪的效果,包括奇怪的异常。

async void函数中,您应该始终通过将所有代码包装到trycatch中来处理任何传入的异常。如果您崩溃了应用程序,如果那里发生了异常,这是您的决定。

事情的一般工作方式是,在Taskasync函数中抛出的任何异常都将由await再次抛出。但是async void函数在任何地方都没有await,所以这不起作用。

关于async。我不认为你需要包装每个功能到一个Task,但你可以这样做。通常你不需要像这样强迫它进入背景

将getRecipeList代码封装在try/catch中。

在catch代码中,获取异常消息和堆栈跟踪,并将它们分配给Recipe类的变量,当操作完成时,您将在主线程中测试/显示这些变量。

private string TheException = "" ;
public Task GetRecipeListAsync()
{
  TheException = "" ;
  Task result = Task.Run(async () => 
  {
    try 
    {  
      //I get the page using WebRequest and WebResponse in the same way as above
      ...
     }
     catch (Exception Ex)  
  }
  if (TheException!="") MessageBox.show(TheException) ; 
  // or Throw an exception with
  return result ; 
}

你也可以在GoClick过程中测试"TheExceptio"