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

时间:2015-07-10 12:24:45

标签: c# io async-await task

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

//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.Run,​​因为Method中有异步和阻塞代码。

//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?

3 个答案:

答案 0 :(得分:2)

  

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

非常接近。我不认为在这种情况下需要Task.Run(它应该只用于从UI线程中移除CPU密集型操作,并且HTML抓取通常足够快以保持在UI上而没有任何不利影响效果)。

我提出的另一个建议是让async Task种方法返回值尽可能多,而不是将成员变量修改为副作用。在您的情况下,请考虑让ScrapeAsync返回自己的列表而不是更新共享列表,并让调用代码执行列表合并。

关于您的例外情况,TargetInvocationException会有InnerException详细信息。 async void事件处理程序中的异常管理与常规void事件处理程序中的异常几乎相同:如果一个事件处理程序转义,则转到应用程序主循环。如果你不想要这个,你必须抓住它(对于同步和异步处理程序)。

答案 1 :(得分:1)

将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”。

答案 2 :(得分:1)

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

你的问题是这个功能:

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();抛出异常,则会在continuation处理程序中抛出此异常。此处理程序在UI的任务队列中执行。抛出异常会导致UI的任务循环崩溃,这会产生各种有趣的效果,包括奇怪的异常。

async void函数中,您应始终通过将所有代码包含在try ... catch中来处理任何传入异常。如果您发现异常,则崩溃应用程序是否由您决定。

事情的一般方式是Task内的任何异常和async函数中的扩展都会被await再次抛出。但是async void函数在任何地方都没有await,所以这不起作用。

关于async的用法。我认为你不需要将每个函数都包含在Task中,但你可以这样做。通常你不需要像这样强迫它进入背景。