我有一个包含食谱列表的网页。在我的代码中,我遍历页面并下载每个配方。这是我正在做的伪代码:
//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?
答案 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
中,但你可以这样做。通常你不需要像这样强迫它进入背景。