Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/259.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 意外的堆栈溢出_C#_.net_Multithreading_Async Await_Task Parallel Library - Fatal编程技术网

C# 意外的堆栈溢出

C# 意外的堆栈溢出,c#,.net,multithreading,async-await,task-parallel-library,C#,.net,Multithreading,Async Await,Task Parallel Library,为什么下面的异步递归会在StackOverflowException中失败,为什么它恰好发生在最后一步,计数器变为零 static async Task<int> TestAsync(int c) { if (c < 0) return c; Console.WriteLine(new { c, where = "before", Environment.CurrentManagedThreadId }); await Task.Yi

为什么下面的异步递归会在
StackOverflowException
中失败,为什么它恰好发生在最后一步,计数器变为零

static async Task<int> TestAsync(int c)
{
    if (c < 0)
        return c;

    Console.WriteLine(new { c, where = "before", Environment.CurrentManagedThreadId });

    await Task.Yield();

    Console.WriteLine(new { c, where = "after", Environment.CurrentManagedThreadId });

    return await TestAsync(c-1);
}

static void Main(string[] args)
{
    Task.Run(() => TestAsync(5000)).GetAwaiter().GetResult();
}
现在,在使用
TaskExt.Yield
而不是
Task.Yield
时,线程每次都在翻转,但堆栈溢出仍然存在:

... { c = 10, where = before, CurrentManagedThreadId = 3 } { c = 10, where = after, CurrentManagedThreadId = 4 } { c = 9, where = before, CurrentManagedThreadId = 4 } { c = 9, where = after, CurrentManagedThreadId = 5 } { c = 8, where = before, CurrentManagedThreadId = 5 } { c = 8, where = after, CurrentManagedThreadId = 3 } { c = 7, where = before, CurrentManagedThreadId = 3 } { c = 7, where = after, CurrentManagedThreadId = 4 } { c = 6, where = before, CurrentManagedThreadId = 4 } { c = 6, where = after, CurrentManagedThreadId = 5 } { c = 5, where = before, CurrentManagedThreadId = 5 } { c = 5, where = after, CurrentManagedThreadId = 4 } { c = 4, where = before, CurrentManagedThreadId = 4 } { c = 4, where = after, CurrentManagedThreadId = 3 } { c = 3, where = before, CurrentManagedThreadId = 3 } { c = 3, where = after, CurrentManagedThreadId = 5 } { c = 2, where = before, CurrentManagedThreadId = 5 } { c = 2, where = after, CurrentManagedThreadId = 3 } { c = 1, where = before, CurrentManagedThreadId = 3 } { c = 1, where = after, CurrentManagedThreadId = 5 } { c = 0, where = before, CurrentManagedThreadId = 5 } { c = 0, where = after, CurrentManagedThreadId = 3 } Process is terminated due to StackOverflowException. ... {c=10,其中=before,CurrentManagedThreadId=3} {c=10,其中=after,CurrentManagedThreadId=4} {c=9,其中=before,CurrentManagedThreadId=4} {c=9,其中=after,CurrentManagedThreadId=5} {c=8,其中=before,CurrentManagedThreadId=5} {c=8,其中=after,CurrentManagedThreadId=3} {c=7,其中=before,CurrentManagedThreadId=3} {c=7,其中=after,CurrentManagedThreadId=4} {c=6,其中=before,CurrentManagedThreadId=4} {c=6,其中=after,CurrentManagedThreadId=5} {c=5,其中=before,CurrentManagedThreadId=5} {c=5,其中=after,CurrentManagedThreadId=4} {c=4,其中=before,CurrentManagedThreadId=4} {c=4,其中=after,CurrentManagedThreadId=3} {c=3,其中=before,CurrentManagedThreadId=3} {c=3,其中=after,CurrentManagedThreadId=5} {c=2,其中=before,CurrentManagedThreadId=5} {c=2,其中=after,CurrentManagedThreadId=3} {c=1,其中=before,CurrentManagedThreadId=3} {c=1,其中=after,CurrentManagedThreadId=5} {c=0,其中=before,CurrentManagedThreadId=5} {c=0,其中=after,CurrentManagedThreadId=3} 进程因StackOverflowException而终止。
第三方物流再次进入:

请注意,堆栈溢出发生在所有迭代完成后的函数末尾。增加迭代计数不会改变这一点。将其降低到一个较小的值可以消除堆栈溢出

堆栈溢出发生在完成方法
TestAsync
的异步状态机任务时。它不会发生在“下降”上。它发生在退出并完成所有
async
方法任务时

让我们首先将计数减少到2000,以减少调试器的负载。然后,查看调用堆栈:

当然是非常重复和漫长的。这是要查看的正确线程。坠机发生在:

        var t = await TestAsync(c - 1);
        return t;
当内部任务
t
完成时,它会导致执行其余的外部
TestAsync
。这只是返回语句。返回完成外部
TestAsync
产生的任务。这再次触发另一个
t
的完成,依此类推

TPL将一些任务延续作为性能优化。这种行为已经引起了很多悲伤,堆栈溢出问题已经证明了这一点。这一问题由来已久,迄今尚未得到任何回应。这并没有激发我们最终摆脱第三方物流再入问题的希望

TPL有一些堆栈深度检查,以在堆栈变得太深时关闭continuations的内联。这不是在这里做的原因(尚未)我不知道。请注意,堆栈上没有
TaskCompletionSource
TaskAwaiter
利用TPL中的内部功能来提高性能。可能优化后的代码路径不执行堆栈深度检查。从这个意义上说,这可能是一个bug

我认为调用
Yield
与问题无关,但最好将其放在这里,以确保
TestAsync
的非同步完成


让我们手动编写异步状态机:

static Task<int> TestAsync(int c)
{
    var tcs = new TaskCompletionSource<int>();

    if (c < 0)
        tcs.SetResult(0);
    else
    {
        Task.Run(() =>
        {
            var t = TestAsync(c - 1);
            t.ContinueWith(_ => tcs.SetResult(0), TaskContinuationOptions.ExecuteSynchronously);
        });
    }

    return tcs.Task;
}

static void Main(string[] args)
{
    Task.Run(() => TestAsync(2000).ContinueWith(_ =>
    {
          //breakpoint here - look at the stack
    }, TaskContinuationOptions.ExecuteSynchronously)).GetAwaiter().GetResult();
}
静态任务测试同步(int c)
{
var tcs=new TaskCompletionSource();
if(c<0)
tcs.SetResult(0);
其他的
{
Task.Run(()=>
{
var t=TestAsync(c-1);
t、 ContinueWith(=>tcs.SetResult(0),TaskContinuationOptions.ExecuteSynchronously);
});
}
返回tcs.Task;
}
静态void Main(字符串[]参数)
{
Task.Run(()=>TestAsync(2000)。继续(=>
{
//断点在这里-查看堆栈
},TaskContinuationOptions.ExecuteSynchronously)).GetAwaiter().GetResult();
}
多亏了
TaskContinuationOptions.ExecuteSynchronously
,我们还希望继续内联能够发生。确实如此,但不会使堆栈溢出:

这是因为TPL防止堆栈变得太深(如上所述)。完成
async
方法任务时,此机制似乎不存在


如果删除了同步执行的
executes
,则堆栈很浅,不会发生内联

很高兴看到你仍然在使用匿名对象串技巧:)@usr,这是我从你那里学来的最爱之一:)一个很好的答案。事实上,这个问题的灵感来自一个客户等待者(我们称之为
AlwaysAsync
),我用它来解决您在这里描述的完全相同的第三方物流问题。我在
TestAsync
中使用它,但不在其返回行中使用。因此,我刚刚将返回行更改为
return-await-TestAsync(c-1)。AlwaysAsync()
问题已经解决:)另一种消除堆栈跳转的方法是使用
Task.Run
async-Task-TestAsync(int-c){if(c<0)return c;return await-Task.Run(()=>TestAsync(c-1));}
@Noseratio这在实践中有效吗?我认为完成可能是一路内联的。Run内部有特殊的性能优化的展开代码。可能这里的堆栈溢出避免机制已经就位并开始工作。实际上,这似乎正在被修复,请参阅。@usr,
Task.Run
无论迭代次数多少都可以工作。我自己也不知道为什么。也许您对
任务的看法是正确的。Unwrap
可能包括避免内联的检查。
        var t = await TestAsync(c - 1);
        return t;
static Task<int> TestAsync(int c)
{
    var tcs = new TaskCompletionSource<int>();

    if (c < 0)
        tcs.SetResult(0);
    else
    {
        Task.Run(() =>
        {
            var t = TestAsync(c - 1);
            t.ContinueWith(_ => tcs.SetResult(0), TaskContinuationOptions.ExecuteSynchronously);
        });
    }

    return tcs.Task;
}

static void Main(string[] args)
{
    Task.Run(() => TestAsync(2000).ContinueWith(_ =>
    {
          //breakpoint here - look at the stack
    }, TaskContinuationOptions.ExecuteSynchronously)).GetAwaiter().GetResult();
}