C# 使用异步代码时获取有意义的堆栈跟踪
我创建了一小段代码,用于并行运行多个异步操作(类本身不适合异步操作) 看起来是这样的:C# 使用异步代码时获取有意义的堆栈跟踪,c#,async-await,C#,Async Await,我创建了一小段代码,用于并行运行多个异步操作(类本身不适合异步操作) 看起来是这样的: public static async Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body) { var chunks = source.Chunk(dop); foreach (var chunk in chunks) await Task
public static async Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body)
{
var chunks = source.Chunk(dop);
foreach (var chunk in chunks)
await Task.WhenAll(chunk.Select(async s => await body(s).ContinueWith(t => ThrowError(t))));
}
private static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
{
while (source.Any())
{
yield return source.Take(chunksize);
source = source.Skip(chunksize);
}
}
private static void ThrowError(Task t)
{
if (t.IsFaulted)
{
if (t.Exception.InnerExceptions != null && t.Exception.InnerExceptions.Count == 1)
throw t.Exception.InnerExceptions[0];
else
throw t.Exception;
}
}
公共静态异步任务ForEachAsync(此IEnumerable源代码,int-dop,Func-body)
{
var chunks=source.Chunk(dop);
foreach(var chunk in chunks)
wait Task.WhenAll(chunk.Select(异步s=>wait body.ContinueWith(t=>ThrowError(t)));
}
私有静态IEnumerable块(此IEnumerable源,int chunksize)
{
while(source.Any())
{
收益返回源。获取(chunksize);
source=source.Skip(chunksize);
}
}
专用静态空隙喷射器(任务t)
{
如果(t.IsFaulted)
{
if(t.Exception.InnerExceptions!=null&&t.Exception.InnerExceptions.Count==1)
抛出t.Exception.InnerExceptions[0];
其他的
抛出t异常;
}
}
就并行运行任务而言,上面的代码工作得非常好。但是,当抛出异常时,我确实有一些问题
异常捕获代码在返回异常消息时运行良好,但堆栈跟踪还有很多需要改进的地方,因为它指向的是ThrowError
方法,而不是最初生成异常的方法。我可以按自己的方式工作,并找出附加的调试器出了什么问题,但如果我发布这个应用程序,我将没有可用的选项-最多,我会记录堆栈跟踪的异常
那么,在运行异步任务时,有没有办法获得更有意义的堆栈跟踪
另外,这是针对WindowsRT应用程序的,但我认为问题不限于WindowsRT本身
那么,在运行异步任务时,有没有办法获得更有意义的堆栈跟踪
是的,您可以使用.NET 4.5中为异步等待引入的ExceptionDispatchInfo.Capture
,具体如下:
private static void ThrowError(Task t)
{
if (t.IsFaulted)
{
Exception exception =
t.Exception.InnerExceptions != null && t.Exception.InnerExceptions.Count == 1
? t.Exception.InnerExceptions[0]
: t.Exception;
ExceptionDispatchInfo.Capture(exception).Throw();
}
}
“您可以使用此方法在另一时间(可能在另一个线程上)返回的ExceptionDispatchInfo
对象来重新引发指定的异常,就像异常从捕获它的点流向重新引发它的点一样。
如果捕获异常时异常处于活动状态,则会存储异常中包含的当前堆栈跟踪信息和Watson信息。如果异常处于非活动状态,也就是说,如果未引发异常,则不会有任何堆栈跟踪信息或Watson信息。”
但是,请记住,异步代码中的异常通常没有您希望的那么有意义,因为所有异常都是从编译器生成的状态机上的
MoveNext
方法中抛出的。i3arnon的答案完全正确,但仍有一些替代方法。您面临的问题是因为throw
是捕获堆栈跟踪的部分-通过再次抛出相同的异常,您已经丢弃了整个堆栈跟踪
避免这种情况的最简单方法是让任务
完成它的工作:
t.GetAwaiter().GetResult();
就是这样-异常是使用正确的堆栈跟踪和所有内容重新启动的。您甚至不必检查任务是否有错误——如果有,它将抛出,如果没有,它将不会抛出
在内部,此方法使用ExceptionDispatchInfo.Capture(exception.Throw()代码>i3arnon已显示,因此它们几乎相等(您的代码假定任务已完成,有无故障-如果尚未完成,IsFaulted
将返回false)
上面的代码工作得非常好
我不确定您为什么希望在线程池中直接抛出异常(ContinueWith
)。这将使您的进程崩溃,而不给任何代码清理的机会
对我来说,一种更自然的方法是让异常冒泡。除了允许自然清理外,此方法还删除了所有笨拙、怪异的代码:
public static async Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body)
{
var chunks = source.Chunk(dop);
foreach (var chunk in chunks)
await Task.WhenAll(chunk.Select(s => body(s)));
}
private static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
{
while (source.Any())
{
yield return source.Take(chunksize);
source = source.Skip(chunksize);
}
}
// Note: No "ThrowError" at all.
公共静态异步任务ForEachAsync(此IEnumerable源代码,int-dop,Func-body)
{
var chunks=source.Chunk(dop);
foreach(var chunk in chunks)
wait Task.WhenAll(chunk.Select(s=>body));
}
私有静态IEnumerable块(此IEnumerable源,int chunksize)
{
while(source.Any())
{
收益返回源。获取(chunksize);
source=source.Skip(chunksize);
}
}
//注意:根本没有“投掷者”。
我知道使用抛出异常
可以消除try catch
代码中的堆栈跟踪。但是在本例中,t.Exception
已经缺少堆栈跟踪(与任何InnerExceptions
一样)。所以,如果我再扔一次也没关系——信息已经丢失了。@Shaaman是的,它只有“真实的”堆栈跟踪,这不是很有用。但是,当您使用t.GetAwaiter().GetResult()
时,将重建“异步”堆栈跟踪。抛出“旧”异常几乎总是一个坏主意-如果捕获了GetResult
的结果,您仍然希望包装异常(或者重新抛出异常而不是抛出异常)。我明白了。谢谢你澄清这一点。是。。。有点困惑。如果它不包含有意义的异常信息,那么t.Exception
有什么好处?哦,好吧…@shaaman好吧,它确实包含除了堆栈跟踪之外的所有异常信息。除非您专门使用异常进行调试,否则这仍然非常有用—例如,如果您希望出现SocketException
,则不关心堆栈跟踪—您只想处理异常。您只关心未处理异常的堆栈跟踪,而这正是获得正确跟踪的地方(除非您自己抛出异常)。但最重要的一点是,Task.Exception
早于async
/wait
-MSR仍然没有时间机器:)这不是重点