2016-09-26 4 views
8

Ich habe ein folgendes Codebeispiel, das in ASP.NET MVC-Anwendung verwendet wird. Der Zweck dieses Codes besteht darin, eine "fire and forget" -Anforderung zum Einreihen in eine lange laufende Operation zu erstellen.ASP.NET HttpContext.Current in Task.Run

public JsonResult SomeAction() { 
    HttpContext ctx = HttpContext.Current;    

    Task.Run(() => { 
     HttpContext.Current = ctx; 
     //Other long running code here. 
    }); 

    return Json("{ 'status': 'Work Queued' }"); 
} 

Ich weiß, dass dies für den Umgang mit HttpContext.Current in asynchronem Code kein guter Weg ist, aber zur Zeit unsere Implementierung erlaubt uns nicht, etwas anderes zu tun. Ich mag würde zu verstehen, wie viel dieser Code ist gefährlich ...

Die Frage: Ist es theoretisch möglich, dass Einstellung der Httpcontext innerhalb Task.Run, den Kontext auf völlig andere Anforderung?

Ich denke ja, aber ich bin mir nicht sicher. Wie ich es verstehe: Request1 wird mit Thread1 aus Thread-Pool behandelt, während Thread1 absolut eine andere Anfrage (Request2) behandelt, wird der Code innerhalb von Task.Run Kontext von Request1 zu Request2 setzen.

Vielleicht irre ich mich, aber mein Wissen über ASP.NET Interna erlaubt mir nicht, es richtig zu verstehen.

Danke!

+3

Wäre es nicht einfacher, nur die Informationen aus dem HttpContext zu holen, die Sie benötigen, anstatt den gesamten HttpContext zu übergeben? (Ich weiß, das beantwortet deine Frage nicht, aber ich bin neugierig auf die Notwendigkeit des ganzen Zusammenhangs). – vcsjones

+2

Richtig, das ist viel richtiger Weg, aber leider kann ich das momentan nicht ändern. Unser Code hat Zugriff auf HttpContext.Current tief in Business-Logik und ändern es ist ein großer Aufwand, den wir derzeit nicht haben. –

+1

Nicht im Zusammenhang mit Ihrer Frage - lange laufende Aufgabe im Web-Kontext ist keine gute Idee - der Server kann neu gestartet werden und es gibt nur so viele Threads im Pool - sobald Sie keine Threads mehr haben, werden Sie aufhören, Anfragen zu bedienen. Hast du etwas wie HangFire oder Quartz in Betracht gezogen? –

Antwort

5

Lassen Sie mich ein wenig Einbauten auf Sie stoßen:

public static HttpContext Current 
{ 
    get { return ContextBase.Current as HttpContext; } 
    set { ContextBase.Current = value; } 
} 

internal class ContextBase 
{ 
    internal static object Current 
    { 
     get { return CallContext.HostContext; } 
     set { CallContext.HostContext = value; } 
    } 
} 

public static object HostContext 
{ 
    get 
    { 
     var executionContextReader = Thread.CurrentThread.GetExecutionContextReader(); 
     object hostContext = executionContextReader.IllogicalCallContext.HostContext; 
     if (hostContext == null) 
     { 
      hostContext = executionContextReader.LogicalCallContext.HostContext; 
     } 
     return hostContext; 
    } 
    set 
    { 
     var mutableExecutionContext = Thread.CurrentThread.GetMutableExecutionContext(); 
     if (value is ILogicalThreadAffinative) 
     { 
      mutableExecutionContext.IllogicalCallContext.HostContext = null; 
      mutableExecutionContext.LogicalCallContext.HostContext = value; 
      return; 
     } 
     mutableExecutionContext.IllogicalCallContext.HostContext = value; 
     mutableExecutionContext.LogicalCallContext.HostContext = null; 
    } 
} 

So

var context = HttpContext.Current; 

gleich (Pseudo-Code)

var context = CurrentThread.HttpContext; 

und in Ihrem Task.Run so etwas passiert

CurrentThread.HttpContext= context; 

Task.Run startet neue Aufgabe mit Thread aus Thread-Pool. Sie sagen also, dass Ihr neuer Thread "HttpContext property" ein Verweis auf den Startthread "HttpContext property" ist - so weit so gut (gut mit allen NullReference/Dispose-Ausnahmen, denen Sie nach dem Start des Starter-Threads begegnen werden).Das Problem ist, wenn in Ihrem

//Other long running code here. 

Sie haben Anweisung wie

var foo = await Bar(); 

Sobald Sie Hit erwarten, Ihren aktuellen Thread-Pool einzufädeln zurückgeführt wird, und nach dem IO beendet Sie greifen neuen Thread aus Thread-Pool - Wunder Was ist seine "HttpContext Eigenschaft", oder? Ich weiß es nicht :) Wahrscheinlich enden Sie mit NullReferenceException.

+0

Vielen Dank für die ausführliche Antwort! Von dem, was Sie geschrieben haben, verstehe ich, dass es technisch passieren kann (Request2 wird den Kontext von Request1 bekommen), aber dann werde ich höchstwahrscheinlich NULL-Referenzen bekommen und Ausnahmen disponieren, weil Request1 beendet ist und ASP.NET seinen Kontext "gelöscht" hat. ..am ich verstehe deine Antwort richtig? Vielen Dank! –

2

Das Problem, auf das Sie hier stoßen werden, ist, dass der HttpContext über die Anfrage verfügt, sobald die Anfrage abgeschlossen ist. Da Sie nicht auf das Ergebnis von Task.Run warten, erstellen Sie im Wesentlichen eine Race-Bedingung zwischen der Beseitigung des HttpContext und seiner Verwendung innerhalb der Aufgabe.

Ich bin mir ziemlich sicher, dass das einzige Problem, auf das Ihre Aufgabe stoßen wird, eine NullReferenceException oder eine ObjectDisposedException ist. Ich sehe keinen Weg, wo du versehentlich den Kontext einer anderen Anfrage stehlen könntest.

Auch, wenn Sie & Protokollierung Ausnahmen innerhalb Ihrer Aufgabe behandeln, wird Ihr Feuer und vergessen werfen und Sie werden nie darüber wissen.

Schauen Sie sich HangFire an oder erwägen Sie, eine Nachrichtenwarteschlange für die Verarbeitung von Backend-Jobs aus einem separaten Prozess zu verwenden.

+0

Danke für eine Antwort. Ich bin mir über Race Condition und mögliche NullReference/Disposed Ausnahme bewusst. Aber ich würde gerne technisch verstehen, warum du denkst, dass dieser Weg keinen anderen Request-Kontext stehlen kann ... wie ich weiß, verwaltet HttpContext.Current Kontexte nach Thread-ID, also wenn zwei Anfragen Thread mit gleicher ID erhalten, kann das HttpContext.Current Gibt den Verweis auf dasselbe Objekt aus zwei Anfragen zurück. –

Verwandte Themen