AspNetSynchronizationContext und warten auf Fortsetzungen in ASP.NET

Ich bemerkte einen unerwarteten (und ich würde sagen, redundanten) Threadwechsel danachawait innerhalb der asynchronen ASP.NET-Web-API-Controller-Methode.

Zum Beispiel würde ich unten erwarten, dasselbe zu sehenManagedThreadId an den Positionen 2 und 3, aber am häufigsten sehe ich einen anderen Thread an # 3:

public class TestController : ApiController
{
    public async Task<string> GetData()
    {
        Debug.WriteLine(new
        {
            where = "1) before await",
            thread = Thread.CurrentThread.ManagedThreadId,
            context = SynchronizationContext.Current
        });

        await Task.Delay(100).ContinueWith(t =>
        {
            Debug.WriteLine(new
            {
                where = "2) inside ContinueWith",
                thread = Thread.CurrentThread.ManagedThreadId,
                context = SynchronizationContext.Current
            });
        }, TaskContinuationOptions.ExecuteSynchronously); //.ConfigureAwait(false);

        Debug.WriteLine(new
        {
            where = "3) after await",
            thread = Thread.CurrentThread.ManagedThreadId,
            context = SynchronizationContext.Current
        });

        return "OK";
    }
}

Ich habe mir die Umsetzung von angeschautAspNetSynchronizationContext.PostIm Wesentlichen kommt es darauf an:

Task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action));
_lastScheduledTask = newTask;

Somit,Die Fortsetzung ist am geplantThreadPool, anstatt inliniert zu werden.&nbsp;Hier,ContinueWith&nbsp;VerwendetTaskScheduler.Current, was meiner Erfahrung nach immer ein Beispiel fürThreadPoolTaskScheduler&nbsp;in ASP.NET (muss aber nicht sein, siehe unten).

Ich könnte einen redundanten Thread-Schalter wie diesen mit beseitigenConfigureAwait(false)&nbsp;oder einen benutzerdefinierten Kellner, aber das würde den automatischen Fluss der Statuseigenschaften der HTTP-Anforderung beeinträchtigenHttpContext.Current.

Es gibt einen weiteren Nebeneffekt der aktuellen Implementierung vonAspNetSynchronizationContext.Post. Im folgenden Fall kommt es zu einem Deadlock:

await Task.Factory.StartNew(
    async () =>
    {
        return await Task.Factory.StartNew(
            () => Type.Missing,
            CancellationToken.None,
            TaskCreationOptions.None,
            scheduler: TaskScheduler.FromCurrentSynchronizationContext());
    },
    CancellationToken.None,
    TaskCreationOptions.None,
    scheduler: TaskScheduler.FromCurrentSynchronizationContext()).Unwrap();

Dieses Beispiel zeigt, wenn auch etwas ausgeklügelt, was passieren kann, wennTaskScheduler.Current&nbsp;istTaskScheduler.FromCurrentSynchronizationContext()hergestellt ausAspNetSynchronizationContext. Es verwendet keinen blockierenden Code und wäre in WinForms oder WPF reibungslos ausgeführt worden.

Dieses Verhalten vonAspNetSynchronizationContext&nbsp;unterscheidet sich von der v4.0 - Implementierung (die noch alsLegacyAspNetSynchronizationContext).

Was ist der Grund für eine solche Änderung?&nbsp;Ich dachte, die Idee dahinter könnte sein, die Lücke für Deadlocks zu verkleinern, aber Deadlocks sind mit der aktuellen Implementierung bei der Verwendung immer noch möglichTask.Wait()&nbsp;oderTask.Result.

IMO, es wäre angemessener, es so auszudrücken:

Task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action),
    TaskContinuationOptions.ExecuteSynchronously);
_lastScheduledTask = newTask;

Zumindest würde ich damit rechnenTaskScheduler.Default&nbsp;eher, alsTaskScheduler.Current.

Wenn ich es aktiviereLegacyAspNetSynchronizationContext&nbsp;mit<add key="aspnet:UseTaskFriendlySynchronizationContext" value="false" />&nbsp;imweb.config, es funktioniert wie gewünscht: Der Synchronisationskontext wird auf dem Thread installiert, auf dem die erwartete Aufgabe beendet wurde, und die Fortsetzung wird dort synchron ausgeführt.