简体   繁体   English

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

[英]Specific exceptions are not shown when using async/await and Task.Run

I have a webpage with a list of recipes. 我有一个包含食谱列表的网页。 In my code, I loop through the page and download each recipe. 在我的代码中,我遍历页面并下载每个配方。 Here is a pseudo code of what I am doing : 这是我正在做的伪代码:

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

I used Task.Run because there are both async and blocking code in the Method. 我使用了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
    }
}

In the form, it loops through a list of pages and do await RecipePage.GetRecipeList() and other things. 在表单中,它循环遍历页面列表,并await RecipePage.GetRecipeList()和其他东西。
Here's where the for loop is: 这是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
    }
}

My problem is that whenever an exception happens in the ScrapeAsync method, Visual Studio 2013 points to Application.Run(new Form1()) 我的问题是,只要ScrapeAsync方法中发生异常,Visual Studio 2013就会指向Application.Run(new Form1())

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

and tells me that Reflection.TargetInvocationException has occured. 并告诉我Reflection.TargetInvocationException已经发生。 It does not show the actual exception in my code. 它没有在我的代码中显示实际的异常。 For example, if I get a NullReferenceException in the code, it does not show that. 例如,如果我在代码中得到NullReferenceException ,则不会显示。 Because of this, I am having to write both async and non-async code and use non-async code to debug. 因此,我必须编写异步和非异步代码,并使用非异步代码进行调试。 Is there any way to solve this? 有什么方法可以解决这个问题吗?
I have another question too. 我也有另一个问题。 Am I using async/await and Task in the correct way? 我是否以正确的方式使用async / await和Task?

Am I using async/await and Task in the correct way? 我是否以正确的方式使用async / await和Task?

Pretty close. 很接近了。 I don't see a need for Task.Run in this scenario (it should only be used to move CPU-intensive operations off the UI thread, and HTML scraping is usually fast enough to stay on the UI without any adverse effects). 我认为在这种情况下不需要Task.Run (它应该仅用于从UI线程中Task.Run CPU密集型操作,并且HTML抓取通常足够快以保持在UI上而没有任何不利影响)。

The other recommendation I'd make is to have async Task methods return values as much as possible, instead of modifying member variables as side effects. 我提出的另一个建议是让async Task方法尽可能多地返回值,而不是将成员变量修改为副作用。 In your case, consider having ScrapeAsync return its own list instead of updating a shared list, and having the calling code do the list merging. 在您的情况下,考虑让ScrapeAsync返回自己的列表而不是更新共享列表,并让调用代码执行列表合并。

Regarding your exception, TargetInvocationException will have an InnerException with the details. 关于您的异常, TargetInvocationException将具有包含详细信息的InnerException Exceptions in async void event handlers are managed in practically the same say as exceptions in regular void event handlers: if one escapes the event handler, then it goes to the application main loop. async void事件处理程序中的异常管理实际上与常规void事件处理程序中的异常相同:如果一个转义事件处理程序,则转到应用程序主循环。 If you don't want this, you'll have to catch it (for both synchronous and asynchronous handlers). 如果你不想这样,你就必须抓住它(对于同步和异步处理程序)。

Encapsulate the getRecipeList code inside a try/catch. 将getRecipeList代码封装在try / catch中。

In the catch code, get exception message and stack trace and assign them to variable(s) of Recipe class that you will test/display in your main thread when action is completed. 在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 ; 
}

you may also test "TheExceptio" in the GoClick procedure. 您也可以在GoClick过程中测试“TheExceptio”。

Okay. 好的。 Thanks to your edits I now can answer. 感谢您的编辑,我现在可以回答。

Your problem is this function: 你的问题是这个功能:

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
    }
}

The issue is that the function returns to what ever calls it once it reaches the await . 问题是该函数一旦到达await就会返回到它所调用的内容。 Now if page.GetRecipeListAsync(); 现在如果page.GetRecipeListAsync(); throws a exception, this exception is thrown inside the continuation handler. 抛出异常,这个异常会在continuation handler中抛出。 This handler is executed in the task queue of your UI. 此处理程序在UI的任务队列中执行。 A exception thrown there crashes the task loop of your UI and this has all sorts of funny effects including strange exceptions. 抛出异常会导致UI的任务循环崩溃,这会产生各种有趣的影响,包括奇怪的异常。

In a async void function you should always handle any incoming exceptions by wrapping all code inside into a trycatch . async void函数中,您应该始终通过将所有代码包装到try ... catch来处理任何传入的异常。 If you crash the application if a exception occurs there or not is yours to decide. 如果您发现异常,则崩溃应用程序是否由您决定。

The general way the things work is that any exception thrown inside a Task and by extend in a async function are thrown again by the await . 事情的工作一般的做法是抛出内部的任何异常Task ,并通过在扩展async功能被再次抛出await But the async void functions are not await ed anywhere, so that does not work. async void函数不会await任何地方await ,所以这不起作用。

Regarding the usage of the async . 关于async的用法。 I don't think you need to wrap every function into a Task but you can do that. 我不认为你需要将每个函数包装到一个Task但你可以这样做。 Usually you don't need to force it into the background like this. 通常你不需要像这样强迫它进入背景。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM