2013-02-15 15 views
7

Ich habe eine MVC4 API REST versucht, einen Prozess in einem neuen Thread zu starten. Ich arbeite mit Framework 4.5 und versuche, sync und clausules zu verwenden.MVC4 WebApi Prozess Launcher

Mein Code sieht so aus:

[AcceptVerbs("POST")] 
public HttpResponseMessage Launch(string id) 
{ 
    runProcessAsync(id); // 1               

    return Request.CreateResponse(HttpStatusCode.Accepted); // 2 
} 

protected async void runProcessAsync(string id) 
{ 
    int exitcode = await runProcess(id); // 3 
    string exitcodesz = string.Format("exitcode: {0}", exitcode); // 4 
    // do more stuff with exitcode 
} 

protected Task<int> runProcess(string id) 
{ 
    var tcs = new TaskCompletionSource<int>(); 

    var process = new Process 
    { 
     StartInfo = { FileName = @"C:\a_very_slow_task.bat" }, 
      EnableRaisingEvents = true 
     }; 
     process.Exited += (sender, args) => tcs.SetResult(((Process)sender).ExitCode);    

     process.Start(); 

     return tcs.Task; 
    } 
} 

Idealerweise jemand einen api Rest Anruf durchführen wird (/ Aufgabe/slowtask/launch) mit Postverb und erwartet sehr schnell ein 202 (angenommen).

Mit Fiddler Web Debugger Ich mache die Anfrage, der Code tritt in Launch (// 1), dann geht auf die Wartezeit (// 3), die langsame Aufgabe erstellt und gestartet und ein Akzeptiert wird zurückgegeben (// 2). Aber zu diesem Zeitpunkt zeigt Fiddler das 202-Ergebnis nicht. Siehe beigefügtes Bild:

http://imageshack.us/photo/my-images/703/fiddler1.png/

Wenn die langsame Aufgabe endet, fährt der Code den Exitcode erfassen (// 4) und dann wird die 202 in Fiddler erfaßt.

Das ist sehr seltsam, weil ich die Rückkehr vor langer Zeit gemacht habe. Was ich vermisse? Wie kann ich diesen Code ändern, um eine 202 sehr schnell zurückzugeben und die Aufgabe zu vergessen?

Hinweis: Ich weiß, wie dies mit nicht Framework 4.5 Funktionen zu tun, ich versuche zu lernen, wie async/erwarten verwenden.

+1

Ich denke, das liegt daran, dass 'runProcessAsync()' auf den Synchronisationskontext der Anfrage läuft. Wenn Sie das nicht möchten, können Sie 'Task.Run (() => runProcessAsync (id));' verwenden. Aber seien Sie vorsichtig damit, denn IIS kann Ihre Anwendungsdomäne während dieser Zeit wiederverwenden, daher besteht die Möglichkeit, dass 'runProcessAsync() 'nicht abgeschlossen wird. – svick

+0

Ich habe das getan und funktioniert. Es ist eine Schande, dass Sie keine richtige Antwort gegeben haben, denn Sie haben mein Problem gelöst. – Jordi

Antwort

4

Ich denke, das ist, weil runProcessAsync() auf den Synchronisationskontext der Anfrage ausgeführt wird. Wenn Sie das nicht möchten, können Sie Task.Run(() => runProcessAsync(id)); verwenden. Aber seien Sie vorsichtig damit, weil IIS Ihre AppDomain während dieser Zeit recyceln kann, also besteht die Möglichkeit, dass runProcessAsync() nicht abgeschlossen wird.

1

Der einfachste Weg ist, einfach runProcessAsync zu ändern, um eine Task zurückzugeben. Denken Sie daran, async Methoden sollten Task/Task<T> wann immer möglich zurückgeben und nur void zurückgeben, wenn sie haben zu.

Allerdings muss ich Sie warnen, dass dies ziemlich gefährlich ist. ASP.NET ist ein HTTP-Server. Wenn keine aktiven Anforderungen vorhanden sind, können Sie Ihre AppDomain bei Bedarf problemlos herunterfahren. Das würde bedeuten, dass dein "Mach mehr Zeug mit Exitcode" einfach von der Oberfläche der Erde fallen wird.

Ich habe eine blog post with a BackgroundTaskManager, die Sie verwenden können, um Task s mit der ASP.NET-Laufzeit zu registrieren. Es wird versucht, AppDomain-Shutdowns zu verzögern, wenn Task s registriert sind, die nicht abgeschlossen wurden. Es ist jedoch nur eine "beste Anstrengung"; es gibt keine Garantien für solche Dinge. Jeder Code, der in ASP.NET ausgeführt wird und keiner aktiven Anforderung zugeordnet ist, ist eine gefährliche Situation.

+0

Ich verstehe nicht, warum sollte ich den Rückgabetyp von 'Task' ändern? Wenn das Problem mit dem Synchronisationskontext zusammenhängt, ändert das nichts, nein? – svick