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();
}
出力:
... { c = 10, where = before, CurrentManagedThreadId = 4 } { 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 = 5 } { c = 7, where = before, CurrentManagedThreadId = 5 } { c = 7, where = after, CurrentManagedThreadId = 5 } { c = 6, where = before, CurrentManagedThreadId = 5 } { c = 6, where = after, CurrentManagedThreadId = 5 } { c = 5, where = before, CurrentManagedThreadId = 5 } { c = 5, where = after, CurrentManagedThreadId = 5 } { c = 4, where = before, CurrentManagedThreadId = 5 } { c = 4, where = after, CurrentManagedThreadId = 5 } { c = 3, where = before, CurrentManagedThreadId = 5 } { c = 3, where = after, CurrentManagedThreadId = 5 } { c = 2, where = before, CurrentManagedThreadId = 5 } { c = 2, where = after, CurrentManagedThreadId = 5 } { c = 1, where = before, CurrentManagedThreadId = 5 } { c = 1, where = after, CurrentManagedThreadId = 5 } { c = 0, where = before, CurrentManagedThreadId = 5 } { c = 0, where = after, CurrentManagedThreadId = 5 } Process is terminated due to StackOverflowException.
私がインストールされている.NET 4.6でこれを見ています。このプロジェクトは、.NET 4.5を対象としたコンソールアプリケーションです。
私は、スレッドが既にプールにリリースされている場合にはTask.Yield
ための継続は、(上記の#5のように)同じスレッド上ThreadPool.QueueUserWorkItem
によってスケジュールを受けることを理解 - 右await Task.Yield()
後、しかしQueueUserWorkItem
コールバックがされている前に、実際に予定されている。
なぜスタックがまだ深刻になっているのか理解できません。たとえ同じスレッドで呼び出されたとしても、ここでは同じスタックフレームで継続が起こるべきではありません。スレッドがそれぞれを反転され、TaskExt.Yield
を使用して代わりにTask.Yield
ながら、
public static class TaskExt
{
public static YieldAwaiter Yield() { return new YieldAwaiter(); }
public struct YieldAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion
{
public YieldAwaiter GetAwaiter() { return this; }
public bool IsCompleted { get { return false; } }
public void GetResult() { }
public void UnsafeOnCompleted(Action continuation)
{
using (var mre = new ManualResetEvent(initialState: false))
{
ThreadPool.UnsafeQueueUserWorkItem(_ =>
{
mre.Set();
continuation();
}, null);
mre.WaitOne();
}
}
public void OnCompleted(Action continuation)
{
throw new NotImplementedException();
}
}
}
今:
は、私はさらに一歩を踏み出したと継続が同じスレッドでは発生しませんを確認します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.
まだ匿名のオブジェクトを使用していることを確認してください。ToString trick :) – usr
@usr、それは私があなたからそれを学んだ以来私のお気に入りの一つです:) – Noseratio