捕获异步操作中的异常

11

我在这里进一步了解异步操作:http://msdn.microsoft.com/en-us/library/hh873173(v=vs.110).aspx

浏览以下示例:

Task<bool> [] recommendations = …;
while(recommendations.Count > 0)
{ 
    Task<bool> recommendation = await Task.WhenAny(recommendations);    
    try
    {
        if (await recommendation) BuyStock(symbol);
        break;
    }
    catch(WebException exc)
    {
        recommendations.Remove(recommendation);
    }
}

我在想,如果我已经在Task.WhenAny上执行了await,为什么还需要在try块内再次使用await

如果我已经这样做了:Task<bool> recommendation = await Task.WhenAny(recommendations); 为什么要这样做:if (await recommendation) BuyStock(symbol);

6个回答

10

第一个await用于异步等待第一个任务完成(即recommendation)。 第二个await仅用于从已经完成的任务中提取实际结果,并抛出存储在任务中的异常。(重要的是要记住,等待已完成的任务已经被优化并将同步执行。

获取结果的另一种选择是使用Task<T>.Result,但它处理异常的方式不同。 await会抛出实际的异常(例如WebException),而Task<T>.Result将抛出包含实际异常的AggregateException

Task<bool> [] recommendations = …;
while(recommendations.Count > 0)
{ 
    Task<bool> recommendation = await Task.WhenAny(recommendations);    
    try
    {
        if (recommendation.Result) 
        {
            BuyStock(symbol);
        }
        break;
    }
    catch(AggregateException exc)
    {
        exc = exc.Flatten();
        if (exc.InnerExceptions[0] is WebException)
        {
            recommendations.Remove(recommendation);
        }
        else
        {
            throw;
        }
    }
}

明显地,等待任务的完成更为简单,因此这是推荐的从任务中检索结果的方式。


1
如果Task由于WebException而出现故障,那么会抛出AggregateException而不是WebException - Servy
2
你现在提到了有所不同,但由于你改变了错误处理语义的方式,导致代码仍然出现了问题。你的回答的关键是,你可以只需更改代码来使用“Result”,这本质上是错误的,因为通过这种更改,你破坏了代码。这个代码的作者之所以在这里使用“await”,是有非常好的理由的。 - Servy
@Servy 我并不建议使用那段代码,它只是回答“为什么要使用await?”问题的一部分。我也进行了澄清。 - i3arnon
@Servy 断言已被删除。这就是澄清。我不同意它没有用。问题是“为什么代码使用await?”答案总是解释了原因。 - i3arnon
不,它没有,因为在这里使用await的整个重点是它不仅仅获取结果。它使用一组特定的错误处理语义来获取结果,这对于方法的功能至关重要。以这样的方式破坏代码,使其无法工作,但以微妙且难以调试的方式无法工作,这几乎是一个毫无用处的答案。此时,您回答中的唯一有用内容基本上是我告诉您需要包含在您回答中的内容,而其中大部分内容都是积极有害的 - Servy
显示剩余3条评论

5
在这里使用await可以创建所需的错误处理语义。如果他使用Result而不是await,那么AggregateException将被直接重新抛出;使用await时,AggregateException中的第一个异常被提取出来,并且该异常被重新抛出。很明显,代码作者希望抛出WebException,而不是他需要手动解包的AggregateException
当然,他也可以使用其他方法。这只是代码作者首选的方法,因为它允许他编写更像传统同步代码的代码,而不是彻底改变代码风格。

4
你说得对。这并不是必要的。你可以用以下内容替换它:

if (recommendation.Result) 
    BuyStock(symbol);

还要注意的是,当传递已完成任务时,await将不会等待(不会设置延续)。 在这种情况下,它仅作为优化同步执行。 我想作者利用了这种优化。
如果你问为什么作者要那样写,也许是为了保持一致性?只有他知道!

1
其他答案已经指出,您必须await await Task.WhenAll返回的任务以解包返回值(或者,您可以使用Result属性)。
但是,您也可以摆脱try/catch(避免捕获不必要的异常是一件好事)。
Task<bool> recommendation = await Task.WhenAny(recommendations);    
if(!recommendation.IsFaulted)
{
    if (await recommendation) BuyStock(symbol);
    break;
}
else
{
    if(recommendation.Exception.InnerExceptions[0] is WebException)
    {
        recommendations.Remove(recommendation);
    }
    else
    {
        throw recommendation.Exception.InnerExceptions[0];
    }
}

你确定当任务失败时,WhenAny不会抛出异常吗? - Sriram Sakthivel
@SriramSakthivel:非常确定。WhenAny返回一个任务,其结果是完成的第一个任务。如果任务成功或失败,则任务已完成。如果任务失败,WhenAny将成功并提供第一个完成的任务(即失败的任务)。 - Falanwe
“recommendation.Exception” 不应该包含一个 “AggregateException” 吗? - i3arnon
@I3arnon:你说得对。我已经相应地更新了我的答案。 - Falanwe

1
如果我已经执行了以下操作:Task recommendation = await Task.WhenAny(recommendations); 为什么要这样做:if (await recommendation) BuyStock(symbol);
因为Task.WhenAny返回一个Task>,而你想要解开外层的Task以获取结果bool。你也可以通过访问返回的Task的Task.Result属性来实现相同的功能。

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接