2016-05-11 2 views
3

Ich habe recherchiert (einschließlich aller anderen SO-Beiträge zu diesem Thema) die beste Möglichkeit, einen (wahrscheinlichsten) Windows-Service-Mitarbeiter zu implementieren, der Elemente von ziehen wird arbeiten aus einer Datenbank und verarbeiten sie parallel asynchron im "Fire-and-Forget" -Modus im Hintergrund (die Workitem-Verwaltung wird in der asynchronen Methode abgewickelt). Die Arbeitselemente werden Webdienstaufrufe und Datenbankabfragen sein. Es wird eine gewisse Drosselung für den Hersteller dieser Arbeitselemente geben, um eine Art von gemessener Vorgehensweise bei der Planung der Arbeit zu gewährleisten. Die folgenden Beispiele sind sehr einfach und dienen nur dazu, die Logik der while-Schleife und der for-Schleife zu verdeutlichen. Welches ist die ideale Methode oder spielt es keine Rolle? Gibt es einen geeigneteren/performanteren Weg dies zu erreichen?Async/Warten oder Task.Run in Konsolenanwendung/Windows-Dienst

async/await ...

private static int counter = 1; 

    static void Main(string[] args) 
    { 
     Console.Title = "Async"; 

     Task.Run(() => AsyncMain()); 

     Console.ReadLine();    
    } 

    private static async void AsyncMain() 
    { 
     while (true) 
     { 
      // Imagine calling a database to get some work items to do, in this case 5 dummy items 
      for (int i = 0; i < 5; i++) 
      { 
       var x = DoSomethingAsync(counter.ToString()); 

       counter++; 
       Thread.Sleep(50); 
      } 

      Thread.Sleep(1000); 
     } 
    } 

    private static async Task<string> DoSomethingAsync(string jobNumber) 
    { 
     try 
     { 
      // Simulated mostly IO work - some could be long running 
      await Task.Delay(5000); 
      Console.WriteLine(jobNumber); 
     } 
     catch (Exception ex) 
     { 
      LogException(ex); 
     } 

     Log("job {0} has completed", jobNumber); 

     return "fire and forget so not really interested"; 
    } 

Task.Run ...

private static int counter = 1; 

    static void Main(string[] args) 
    { 
     Console.Title = "Task"; 

     while (true) 
     { 
      // Imagine calling a database to get some work items to do, in this case 5 dummy items 
      for (int i = 0; i < 5; i++) 
      { 
       var x = Task.Run(() => { DoSomethingAsync(counter.ToString()); }); 

       counter++; 
       Thread.Sleep(50); 
      } 

      Thread.Sleep(1000); 
     } 
    } 

    private static string DoSomethingAsync(string jobNumber) 
    { 
     try 
     { 
      // Simulated mostly IO work - some could be long running 
      Task.Delay(5000); 
      Console.WriteLine(jobNumber); 
     } 
     catch (Exception ex) 
     { 
      LogException(ex); 
     } 

     Log("job {0} has completed", jobNumber); 

     return "fire and forget so not really interested"; 
    } 
+1

Ihr gesamter Code ist falsch. Verwenden Sie nicht 'async void'; Verwenden Sie keine Asynchronität, es sei denn, Sie haben tatsächlich asynchrone Operationen, führen Sie keine asynchronen Dinge aus, ohne auf die. – SLaks

+2

Sie müssen die Grundlagen von async lernen. Lesen Sie https://msdn.microsoft.com/en-us/magazine/jj991977.aspx – SLaks

+0

@SLaks -> Also, wie würden Sie gehen im Wesentlichen mehrere Aufgaben parallel in einem einzigen Prozess wie folgt durchführen? – user2231663

Antwort

2

Pull Artikel von Arbeit aus einer Datenbank und verarbeiten sie parallel asynchron in einer 'fire-and-forget' Art und Weise im Hintergrund

Technisch wollen Sie Gleichzeitigkeit. Ob Sie asynchrone Gleichzeitigkeit oder parallel Gleichzeitigkeit bleibt abzuwarten ...

Die Workitems werden Web-Service-Anrufe und Datenbankabfragen werden.

Die Arbeit ist I/O-gebunden ist, so dass impliziert asynchrone Gleichzeitigkeit als natürlicher Ansatz.

Es wird eine Drosselung für den Hersteller dieser Arbeitselemente angewendet, um eine Art von Ansatz zur Planung der Arbeit zu gewährleisten.

Die Idee eines Erzeuger/Verbraucher-Warteschlange hier impliziert. Das ist eine Option. TPL Dataflow bietet einige nette Producer-/Consumer-Warteschlangen, die async-kompatibel sind und Throttling unterstützen.

Alternativ können Sie die Drosselung selbst vornehmen. Für asynchronen Code gibt es einen integrierten Drosselmechanismus namens SemaphoreSlim.


TPL Datenfluß-Ansatz, mit Drosselung:

private static int counter = 1; 

static void Main(string[] args) 
{ 
    Console.Title = "Async"; 
    var x = Task.Run(() => MainAsync()); 
    Console.ReadLine();   
} 

private static async Task MainAsync() 
{ 
    var blockOptions = new ExecutionDataflowBlockOptions 
    { 
    MaxDegreeOfParallelism = 7 
    }; 
    var block = new ActionBlock<string>(DoSomethingAsync, blockOptions); 
    while (true) 
    { 
    var dbData = await ...; // Imagine calling a database to get some work items to do, in this case 5 dummy items 
    for (int i = 0; i < 5; i++) 
    { 
     block.Post(counter.ToString()); 
     counter++; 
     Thread.Sleep(50); 
    } 
    Thread.Sleep(1000); 
    } 
} 

private static async Task DoSomethingAsync(string jobNumber) 
{ 
    try 
    { 
    // Simulated mostly IO work - some could be long running 
    await Task.Delay(5000); 
    Console.WriteLine(jobNumber); 
    } 
    catch (Exception ex) 
    { 
    LogException(ex); 
    } 
    Log("job {0} has completed", jobNumber); 
} 

Asynchronous Gleichzeitigkeit Ansatz mit manueller Drosselung:

private static int counter = 1; 
private static SemaphoreSlim semaphore = new SemaphoreSlim(7); 

static void Main(string[] args) 
{ 
    Console.Title = "Async"; 
    var x = Task.Run(() => MainAsync()); 
    Console.ReadLine();   
} 

private static async Task MainAsync() 
{ 
    while (true) 
    { 
    var dbData = await ...; // Imagine calling a database to get some work items to do, in this case 5 dummy items 
    for (int i = 0; i < 5; i++) 
    { 
     var x = DoSomethingAsync(counter.ToString()); 
     counter++; 
     Thread.Sleep(50); 
    } 
    Thread.Sleep(1000); 
    } 
} 

private static async Task DoSomethingAsync(string jobNumber) 
{ 
    await semaphore.WaitAsync(); 
    try 
    { 
    try 
    { 
     // Simulated mostly IO work - some could be long running 
     await Task.Delay(5000); 
     Console.WriteLine(jobNumber); 
    } 
    catch (Exception ex) 
    { 
     LogException(ex); 
    } 
    Log("job {0} has completed", jobNumber); 
    } 
    finally 
    { 
    semaphore.Release(); 
    } 
} 

Als abschließende Bemerkung, die ich je kaum empfehlen my own book auf SO Aber ich denke, es würde dir wirklich nützen. Insbesondere Abschnitte 8.10 (Blocking/Asynchronous Queues), 11.5 (Throttling) und 4.4 (Throttling Dataflow Blocks).

0

Zunächst einmal wollen wir einige beheben.

Im zweiten Beispiel werden Sie

Task.Delay(5000); 

ohne await aufrufen. Es ist eine schlechte Idee. Es erstellt eine neue Instanz Task, die für 5 Sekunden ausgeführt wird, aber niemand wartet darauf. Task.Delay ist nur nützlich mit await. Bitte beachten Sie, verwenden Sie nicht Task.Delay(5000).Wait() oder Sie werden festgefahren.

In Ihrem zweiten Beispiel versuchen Sie, die DoSomethingAsync Methode synchron zu machen, lässt es DoSomethingSync anrufen und ersetzen die Task.Delay(5000); mit Thread.Sleep(5000);

Jetzt, fast die alten Schule das zweite Beispiel ThreadPool.QueueUserWorkItem ist. Und es ist nichts schlimmes daran, falls Sie nicht bereits eine bereits asynchrone API verwenden. Task.Run und ThreadPool.QueueUserWorkItem im Feuer-und-vergessen-Fall sind genau das gleiche. Ich würde letzteres für Klarheit verwenden.

Dies treibt uns langsam auf die Antwort auf die Hauptfrage. Asynchron oder nicht asynchron - das ist die Frage! Ich würde sagen: "Erstellen Sie keine asynchronen Methoden für den Fall, dass Sie keine asynchrone IO in Ihrem Code verwenden müssen". Wenn es jedoch eine async API gibt, die Sie verwenden müssen, wäre der erste Ansatz eher von denen erwartet, die Ihren Code Jahre später lesen werden.

Verwandte Themen