2015-07-10 135 views
4

我有一个包含配方列表的网页。在我的代码中,我遍历页面并下载每个配方。下面是我在做什么伪代码:使用async/await和Task.Run时不显示特定异常

//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,​​因为有两个异步和阻止代码的方法。

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

+0

调用'GetRecipeList'的代码是怎样的? – i3arnon

+0

我编辑了这个问题。 –

+0

我知道这样的例外发生,如果抛出的异常无处可去。这可能会发生,如果它是从'await'内部的一个返回'void'而不是'Task'的函数内部引发的。如果你的食谱加载循环在这样的功能? – Nitram

回答

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

async void功能,你应该总是通过包内所有的代码放到一个try ... catch处理任何传入例外。如果您发生异常或者没有发生异常,您应该使应用程序崩溃。

工作的一般方法是Task内部抛出的任何异常以及async函数中的扩展均由await再次抛出。但async void函数不是在任何地方编辑await,所以这是行不通的。

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

1

在try/catch中封装getRecipeList代码。

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

+0

请你详细说明一下吗?我不擅长异常投掷和重新投掷东西 –

+0

我在我的答案中添加了一些代码。 – Graffito

2

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

非常接近。在这种情况下,我没有看到需要Task.Run(它只能用于将CPU密集型操作从UI线程移出,而HTML抓取通常足够快以保留在UI上而没有任何不利影响)。

我建议的其他建议是尽可能使方法返回值,而不是将成员变量修改为副作用。在你的情况下,考虑让ScrapeAsync返回自己的列表,而不是更新共享列表,并让调用代码进行列表合并。

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