使用async / await和TaskCompletionSource进行奇怪的堆栈跟踪增长

时间:2017-09-28 10:48:46

标签: c# .net asynchronous async-await task-parallel-library

以下C#代码:

class Program
{
    static readonly List<TaskCompletionSource<bool>> buffer = 
                new List<TaskCompletionSource<bool>>();
    static Timer timer;

    public static void Main()
    {
        var outstanding = Enumerable.Range(1, 10)
            .Select(Enqueue)
            .ToArray();

        timer = new Timer(x => Flush(), null, 
                         TimeSpan.FromSeconds(1),
                         TimeSpan.FromMilliseconds(-1));
        try
        {
            Task.WaitAll(outstanding);
        }
        catch {}

        Console.ReadKey();
    }

    static Task Enqueue(int i)
    {
        var task = new TaskCompletionSource<bool>();
        buffer.Add(task);
        return task.Task;
    }

    static void Flush()
    {
        try
        {
            throw new ArgumentException("test");
        }
        catch (Exception e)
        {
            foreach (var each in buffer)
            {
                var lenBefore = e.StackTrace.Length;
                each.TrySetException(e);
                var lenAfter = e.StackTrace.Length;
                Console.WriteLine($"Before - After: {lenBefore} - {lenAfter}");
                Console.WriteLine(e.StackTrace);

            }
        }
    }
}

产地:

Before - After: 149 - 149
Before - After: 149 - 149
Before - After: 149 - 149
Before - After: 149 - 149
Before - After: 149 - 149
Before - After: 149 - 149
Before - After: 149 - 149
Before - After: 149 - 149
Before - After: 149 - 149
Before - After: 149 - 149

但是当我将Enqueue方法更改为async:

static async Task Enqueue(int i)
{
    var task = new TaskCompletionSource<bool>();
    buffer.Add(task);
    return await task.Task;
}

结果是:

Before - After: 149 - 643
Before - After: 643 - 1137
Before - After: 1137 - 1631
Before - After: 1631 - 2125
Before - After: 2125 - 2619
Before - After: 2619 - 3113
Before - After: 3113 - 3607
Before - After: 3607 - 4101
Before - After: 4101 - 4595
Before - After: 4595 - 5089

对于每个缓冲的项目,它看起来像递归堆栈跟踪增长。对于第一个项目,异常堆栈跟踪将是:

   at Program.Flush() in C:\src\Program.cs:line 41
   --- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotificati...
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Program.<Enqueue>d__3.MoveNext() in C:\src\Program.cs:line 34

虽然第二个看起来像下面那样,等等:

   at Program.Flush() in C:\src\Program.cs:line 41
   --- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotificati...
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Program.<Enqueue>d__3.MoveNext() in C:\src\Program.cs:line 34
   --- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotificati...
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Program.<Enqueue>d__3.MoveNext() in C:\src\Program.cs:line 34

这里发生了什么以及如何解决?

2 个答案:

答案 0 :(得分:7)

简短回答:await尝试解包结果,而方法withod await不会尝试访问任务结果。

更长的答案:

  1. 调用堆栈的重复部分具有以下外观:
  2. RecurrringCallStack

    1. TaskAwaiter的{​​{3}}方法正在内联,ValidateEnd会调用HandleNonSuccessAndDebuggerNotification,这似乎也是内联的,因为单个例外用于设置10 TaskCompletionSource s,ThrowForNonSuccess
    2. 的结果

      简单的解决方案是在每次new Exception("Some descriptive message", originalException)调用时使用TrySetException

答案 1 :(得分:1)

问题是你正在为每个任务重复使用相同的异常,所以它会将所有堆栈附加在一起,假设这是他们进展的序列。

如果您为每个

创建一个新例外
var ex = new Exception(e.Message, e);
each.TrySetException(ex);

然后你会得到

Before - After: 87 - 341
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Program.<Enqueue>d__3.MoveNext() in C:\src\Program.cs:line 34
Before - After: 87 - 341
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Program.<Enqueue>d__3.MoveNext() in C:\src\Program.cs:line 34
Before - After: 87 - 341
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Program.<Enqueue>d__3.MoveNext() in C:\src\Program.cs:line 34
Before - After: 87 - 341

如果您还使用Ben.Demystifier

var ex = new Exception(e.Message, e);
each.TrySetException(ex);
var lenAfter = ex.Demystify().StackTrace.Length;

然后这将进一步下降:

Before - After: 87 - 105
   at async Task Program.Enqueue(int i) in C:\src\Program.cs:line 34
Before - After: 92 - 105
   at async Task Program.Enqueue(int i) in C:\src\Program.cs:line 34
Before - After: 92 - 105
   at async Task Program.Enqueue(int i) in C:\src\Program.cs:line 34
Before - After: 92 - 105