2013-08-21 8 views
8

Ich habe versucht, Async-Methoden zu lesen, und versuche nun, meine eigene Async-Methode zu erstellen. Die Methode ist ein Webservice-Aufruf, der eine Liste von Fehlerprotokollen zurückgibt. Ich bin mir nicht sicher, ob ich es richtig verstanden habe, also dachte ich, ich würde meinen Code teilen, um zu sehen, ob ich etwas anderes machen sollte.Erstellen einer asynchronen Webservice-Methode

Ich möchte nur, dass der Code eine Liste von Fehlerprotokollen zurückgibt, indem eine Methode aufgerufen wird GetAllErrorLogs(), das ist eine synchronisierte Methode. Da es eine Sekunde dauern kann, um alle Fehlerprotokolle zu holen, möchte ich die Möglichkeit haben, andere Dinge zu tun, sobald ich die GetAllErrorLogs() Methode aufgerufen habe. Hier ist der Code.

[WebMethod] 
public async Task<List<ErrorLog>> GetAllErrorLogs() 
{ 
    List<ErrorLog> errorLogs = new List<ErrorLog>(); 

    await System.Threading.Tasks.Task.Run(() => { 
     errorLogs = ErrorLogRepository.GetAllErrorLogs(); 
    }); 


    if (errorLogs == null) 
     return new List<ErrorLog>(); 

    return errorLogs; 
} 

Vielen Dank!

+1

Ich habe nicht viel Vorteil mit Asynchron/warten auf der Serverseite sehen. Sie verwenden nur mehr Threads für die gleiche Sache. – I4V

+4

@ I4V: 'async' auf der Serverseite kann die Anzahl der Threads, die pro Anfrage verwendet werden, dramatisch reduzieren * (unter der Annahme, dass der Code von Natur aus asynchron und nicht fälschungsasynchron ist wie' Task.Run'). Als Ergebnis können asynchrone Server viel besser skalieren, oft in der Größenordnung von 10-100x. –

+0

Stephan Cleary hat Recht ... Ich habe an einem Trainingskurs auf einem Microsoft-Campus teilgenommen, wo uns gesagt wurde, dass Sie durch die asynchrone Ausführung des gesamten auf Ihrem Server ausgeführten Codes massive Kapazitätserweiterungen für die gleiche Hardware erhalten würden. Nur weil eine Methode auf eine Antwort von einem Unteraufruf wartet, wird der Hauptthread freigegeben, um andere Arbeiten auszuführen ... dh mit anderen gleichzeitigen Webanforderungen umzugehen. Der Vorteil ist Multitasking. – GPR

Antwort

7

Ich hielt vor kurzem einen Vortrag bei ThatConference auf async on the server side, und ich adressiere dieses Problem in den Folien.

Auf der Serverseite möchten Sie die Verwendung von Task.Run und anderen Konstrukten, die in der Warteschlange arbeiten, vermeiden. Halten Sie Thread-Pool-Threads so weit wie möglich für die Verarbeitung von Anforderungen bereit.

Also idealerweise würde Ihr Repository eine asynchrone Methode GetAllErrorLogsAsync haben, die selbst asynchron wäre. Wenn GetAllErrorLogs nicht asynchron sein kann, dann können Sie es auch direkt aufrufen (Entfernen der await Task.Run).

Da es eine Sekunde dauern kann, um alle Fehlerprotokolle zu holen, möchte ich die Möglichkeit haben, andere Sachen zu machen, sobald ich die GetAllErrorLogs() Methode aufgerufen habe.

Wenn Sie einen GetAllErrorLogsAsync zur Verfügung haben, kann dies einfach mit Task.WhenAll durchgeführt werden. Wenn GetAllErrorLogs jedoch synchron ist, können Sie dies nur tun, indem Sie in Ihrer Anforderung parallel arbeiten (z. B. mehrere Aufrufe an Task.Run, gefolgt von Task.WhenAll).

Parallelcode auf dem Server muss mit großer Beklemmung angegangen werden. Dies ist nur in sehr begrenzten Szenarien akzeptabel. Der gesamte Punkt async auf der Serverseite ist weniger Threads pro Anfrage zu verwenden, und wenn Sie mit der Parallelisierung beginnen, tun Sie das Gegenteil: mehrere Threads pro Anfrage. Dies ist nur sinnvoll, wenn Sie wissen, dass Ihre Benutzerbasis sehr klein ist. Andernfalls werden Sie die Skalierbarkeit Ihres Servers beeinträchtigen.

+0

Danke für die Antwort! – Andreas

+0

Die Kompliziertheiten der Parallelität sind zu lang für hier und off-topic, aber es sollte angemerkt werden, dass die Warnung vor Parallelismus hier übertrieben ist. Wenn Sie auf eine Webanforderung an einen Webservice und einen Datenbankaufruf warten, nutzen Sie I/O-Completion-Port-Threads, die auf OS-Ebene sehr günstig sind und keine Threads vom ThreadPool verbrauchen. Ein perfektes Szenario für Parallelität, das Dinge beschleunigt, ohne den Request Threadpool zu manipulieren. Die richtige Antwort wäre hier "Hier ist wie" und nicht "Tu dies nicht". http://StackOverflow.com/a/539968/176877 –

+0

@ChrisMoschini: Ich mache eine Unterscheidung zwischen * Multithreading/Parallelität * ('Task.Run',' Parallel', etc) und * asynchrony * ('async',' erwarten ", usw.), die beide [verschiedene Formen von * Nebenläufigkeit *] sind (https://pbs.twimg.com/media/B63AADfIgAA4jPH.jpg:large). Mit diesen Definitionen ist die Parallelität auf der Serverseite schlecht. –

0

** Diese potenziell falsch ist, lesen Sie Kommentare oder Spin-off Frage bei HttpContext.Current after an await

Wenn ErrorLogRepository.GetAllErrorLogs() zu erreichen, ist nicht Thread -safe, es wird seltsame Bugs verursachen und möglicherweise ausschließen. Stellen Sie sicher, dass Ihr Code für Multithread-Operationen bereit ist, bevor Sie zu asynchronen Methoden wechseln. Dies ist offensichtlich eine sehr triviale Empfehlung, wird aber oft übersehen. Wenn Sie beispielsweise in Ihren Methoden auf HttpContext.Current verweisen, wird Ihr Code in der asynchronen Methode und manchmal sogar nach await sterben. Der Grund dafür ist, dass der Code innerhalb des asynchronen Blocks möglicherweise in einem separaten Thread ausgeführt wird, der nicht auf dieselbe HttpContext.Current thread-static -Eigenschaft zugreifen kann, und await wird in zwei Methoden kompiliert. Der gesamte Code vor einer await wird in einem Thread ausgeführt und ruft den Code nach einem await-Schlüsselwort als Fortsetzung auf, möglicherweise jedoch auch in einem anderen Thread. Manchmal funktioniert Ihr Code sogar in einem asynchronen Block, nur um unerwartet zu ersticken, wenn er aus dem Async zurückkommt, was Sie für einen synchronen Teil Ihres Codes halten (aber in Wirklichkeit ist alles nach einem await Schlüsselwort nicht garantiert) der ursprüngliche Thread sein).

+0

In dieser Antwort gibt es viele Fehlinformationen. 'UnobservedTaskException' ist hier nicht erforderlich (' awaward' wird alle Ausnahmen von 'GetAllErrorLogs' über die WebAPI-Methode korrekt weitergeben). 'HttpContext.Current' wird ordnungsgemäß an alle Threads weitergegeben, die standardmäßig eine asynchrone Anfrage bearbeiten (d. H. Die 'return errorLogs'-Zeile hat eine absolut gültige' HttpContext.Current'). –

+0

Sie haben Recht, warten Sie, ich habe diesen Teil falsch gelesen. Es würde nur dann nicht weitergegeben werden, wenn Task.Run als Feuer benutzt und vergessen wird. HttpContext.Current ist jedoch nach dem Warten definitiv nicht gültig, es sei denn, WebAPI hat das Standardkontextverhalten für den Synchronisierungskontext außer Kraft gesetzt. – welegan

+1

WebAPI stellt keinen Synchronisationskontext bereit, ASP.NET jedoch. So ist 'HttpContext.Current' nach dem' gonna' absolut gültig. –

0

Hier finden Sie einige Produktionscode ...

using System.Web.Http; 
using AysncTask = System.Threading.Tasks.Task; 

public class myController : ApiControllerBase 
{ 
     [HttpPut] 
     [Route("api/cleardata/{id}/{requestId}/")] 
     public async AysncTask ClearData(Guid id, Guid requestId) 
     { 
      try 
      { 
       await AysncTask.Run(() => DoClearData(id, requestId)); 
      } 
      catch (Exception ex) 
      { 
       throw new Exception("Exception in myController.ClearData", ex); 
      } 
     } 
} 
0

Ausnahmen Async Handhabung sehr wichtig auch sehr .. ist, obwohl dies für eine Windows-Konsole app ist, sollten die gleichen Grundsätze gelten.

Quelle: https://blogs.msdn.microsoft.com/ptorr/2014/12/10/async-exceptions-in-c/

using System; 
    using System.Runtime.CompilerServices; 
    using System.Threading; 
    using System.Threading.Tasks; 

    namespace AsyncAndExceptions 
    { 
class Program 
{ 
    static void Main(string[] args) 
    { 
    AppDomain.CurrentDomain.UnhandledException += (s, e) => Log("*** Crash! ***", "UnhandledException"); 
    TaskScheduler.UnobservedTaskException += (s, e) => Log("*** Crash! ***", "UnobservedTaskException"); 

    RunTests(); 

    // Let async tasks complete... 
    Thread.Sleep(500); 
    GC.Collect(3, GCCollectionMode.Forced, true); 
    } 

    private static async Task RunTests() 
    { 
    try 
    { 
     // crash 
     // _1_VoidNoWait(); 

     // crash 
     // _2_AsyncVoidAwait(); 

     // OK 
     // _3_AsyncVoidAwaitWithTry(); 

     // crash - no await 
     // _4_TaskNoWait(); 

     // crash - no await 
     // _5_TaskAwait(); 

     // OK 
     // await _4_TaskNoWait(); 

     // OK 
     // await _5_TaskAwait(); 
    } 
    catch (Exception ex) { Log("Exception handled OK"); } 

    // crash - no try 
    // await _4_TaskNoWait(); 

    // crash - no try 
    // await _5_TaskAwait(); 
    } 

    // Unsafe 
    static void _1_VoidNoWait() 
    { 
    ThrowAsync(); 
    } 

    // Unsafe 
    static async void _2_AsyncVoidAwait() 
    { 
    await ThrowAsync(); 
    } 

    // Safe 
    static async void _3_AsyncVoidAwaitWithTry() 
    { 
    try { await ThrowAsync(); } 
    catch (Exception ex) { Log("Exception handled OK"); } 
    } 

    // Safe only if caller uses await (or Result) inside a try 
    static Task _4_TaskNoWait() 
    { 
    return ThrowAsync(); 
    } 

    // Safe only if caller uses await (or Result) inside a try 
    static async Task _5_TaskAwait() 
    { 
    await ThrowAsync(); 
    } 

    // Helper that sets an exception asnychronously 
    static Task ThrowAsync() 
    { 
    TaskCompletionSource tcs = new TaskCompletionSource(); 
    ThreadPool.QueueUserWorkItem(_ => tcs.SetException(new Exception("ThrowAsync"))); 
    return tcs.Task; 
    } 
    internal static void Log(string message, [CallerMemberName] string caller = "") 
    { 
    Console.WriteLine("{0}: {1}", caller, message); 
    } 
} 

}

Verwandte Themen