2017-08-08 5 views
0

Ich habe gerade angefangen, mit Aufgaben zu arbeiten. Wir haben ein System-Setup, das Anfragen/Antworten verwendet. Der Dienst, auf dem die Aufgaben ausgeführt werden, akzeptiert eine Masteranforderung mit einer Liste von Anforderungsobjekten und gibt eine Masterantwort mit einer Liste von Antwortobjekten zurück. So sieht es etwa so ausRelating Aufgabe Ausnahme zu einer Aufgabe <T> Antwort

var MasterRequest = new MasterRequest; 
    MasterRequest.Requests.Add(new BlueRequest); 
    MasterRequest.Requests.Add(new RedRequest); 
    MasterRequest.Requests.Add(new YellowRequest); 

Die Anfrage implementiert eine einfache IRequest-Schnittstelle und jede Farbe ist eine konkrete Klasse. Der Dienst hat konkrete Klassen (Anfrageprozessoren) eingerichtet, um jede Anfrage getrennt und gleichzeitig nach einem konkreten Anfrageobjekt bearbeiten zu können. Jede konkrete Klasse auf den Dienst hat eine GetTask Methode mit einer Signatur wie folgt aus:

Task<IResponse> GetTask(IRequest); 
    { 
     // some setup stuff 
     return Task.Factory.StartNew<IResponse>(() => 
     { 
      // do task stuff 
      return response; // implements IResponse 
     });    
    } 

Mein Service nimmt die in MasterRequest weitergegeben und erstellt eine Liste von Aufgaben, die von den GetTask Aufruf Aufruf über den konkreten Anfrage Prozessoren aufgeführt. Ich verwende dann eine Parallel.ForEach auf der Liste, um die Aufgaben zu bearbeiten.

// this is what is returned from the service. 
    // it has a List<IResponse> on it to hold the resposnes 
    MasterResposne resposne = new MasterResponse(); 

    List<Task<IResponse>> tasks = new List<Task<IResponse>>(); 

    foreach(IRequest req in MasterRequest.Requests) 
    { 
     // factory to get the proper request processor 
     RequestProcessor p = rp.GetProcessor(req); 

     tasks.add(p.GetTask(req)); 
    } 

    Parallel.ForEach(tasks, t => 
     { 
      t.Wait(); 

       // check for faulted and cancelled 
       // this is where I need help 

       response.Responses.Add(t.Result); 
     } 

Das alles funktioniert super. Aber wenn die Aufgabe eine Ausnahme auslöst, weiß ich nicht, wie ich sie an die spezifische konkrete Anfrage binden kann, die sie ausgelöst hat. Ich muss es wissen, damit ich dem Anrufer eine korrekt aufgebaute Antwort zurückgeben kann.

Mein erster Gedanke war, Task zu Unterklasse, aber das bringt seine eigene Reihe von Problemen, mit denen ich nicht umgehen möchte.

Ich las diesen SO Artikel und es scheint, wie ich so etwas wie dieses

Is this ok to derive from TPL Task to return more details from method?

Ich denke, Reeds zweites Beispiel tun möchte, ist meine Lösung, aber ich kann immer noch nicht sehen, wie die Aufgaben gleichzeitig ausgeführt werden und sein Ich bin in der Lage, Ausnahmen mit der Anfrage zu verknüpfen, so dass ich eine korrekt erstellte Liste von Antworten zurückgeben kann.

Vielen Dank im Voraus.

+0

1) Ich denke, der empfohlene Weg, um eine Aufgabe zu starten, ist Task.Run() 2) Sie können in TaskCompletionSource graben. Hier einige Links, die mir sehr geholfen haben: https://msdn.microsoft.com/en-us/magazine/jj991977.aspx https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming- Muster/Interop mit anderen asynchronen Mustern und Typen # EAP https://scalablenotions.wordpress.com/2015/05/02/tpl-and-async-await-best-practices-for-the-busy -developer/ – Fildor

+5

Vielleicht ist Task.WaitAll() besser als Parallel? – ASpirin

+0

@Fildor das ist ein guter Link. Ich habe einige andere Anforderungen, die mich zwingen, einen benutzerdefinierten Aufgabenplaner zu haben, so dass ich gezwungen bin, den Task.Factory.StarNew-Aufruf zu verwenden. – brian

Antwort

0

So konnte ich Reeds Lösung von dem Link, den ich lieferte, verwenden. Mein Service Code, um die Anfragen verwandelte sich in diesen

// this is what is returned from the service. 
// it has a List<IResponse> on it to hold the resposnes 
MasterResposne resposne = new MasterResponse(); 

List<ExecutionResult> tasks = new List<ExecutionResult>(); 

foreach(IRequest req in MasterRequest.Requests) 
{ 
    // factory to get the proper request processor 
    RequestProcessor p = rp.GetProcessor(req); 

    tasks.add(p.GetResult(req)); 
} 

Parallel.ForEach(tasks, t => 
    { 
     t.task.Wait(); 

      response.Responses.Add(t.Result); 
    } 

Wo ExecutionResult definiert ist, wie so

class ExecutionResult 
    { 
     public IResult Result; 
     public Task<IResponse> task; 

    } 

Das gibt mir Zugang zu einem vordefinierten Antwortobjekt zu verarbeiten, so kann ich es zurück an den Aufrufer .

EDIT: So wiederholte ich meine Parallel.ForEach und konnte meinen Code und die Verwendung wiederholen Task.WhenAll erwarten, wie vorgeschlagen. Neuer Code sieht wie folgt aus mehr:

// this is what is returned from the service. 
    // it has a List<IResponse> on it to hold the resposnes 
    MasterResposne resposne = new MasterResponse(); 

    List<ExecutionResult> tasks = new List<ExecutionResult>(); 
    List<ExecutionResult> executionResults = new List<ExecutionResult>(); 

    foreach(IRequest req in MasterRequest.Requests) 
    { 
     // factory to get the proper request processor 
     RequestProcessor p = rp.GetProcessor(req); 

     ExecutionResult er = engine.GetResult(req); 
     executionResults.Add(er); 
     tasks.Add(er.Task); 
    } 

     await Task.WhenAll<IResponse>(tasks); 

     foreach (ExecutionResult r in executionResults) 
     { 
      if (r.Task.IsCompleted) 
      { 
       response.AddResponse(r.Task.Result); 
      } 
      else 
      { 
       r.Response.Status = false; 
       AggregateException flat = r.Task.Exception.Flatten(); 

       foreach (Exception e in flat.InnerExceptions) 
       { 
        Log.ErrorFormat("Reqest [{0}] threw [{1}]", r.Response.RequestId, e); 
        r.Response.StatusReason.AppendLine(e.Message); 
       } 
      } 
     } 

Dies ermöglicht es mir meine Anfrage Informationen an meine Aufgabe zu binden und die Antwort zurück, dass ich zu meinem Anrufer zurückkommen müssen.

Danke für die Anleitung.

+0

Verwenden von 'Task.Wait()' für mehrere Aufgaben in Kombination mit 'Parallel.ForEach' ist das Seltsamste, was ich bisher in Bezug auf das Warten auf Aufgaben zu tun hatte. Gibt es einen guten Grund, 'awarte Task.WhenAll()' nicht zu verwenden? –

+0

Auch ist die teilweise Beantwortung Ihrer eigenen Frage nicht sehr hilfreich, können Sie stattdessen die ursprüngliche Frage aktualisieren, damit eine neue Person, die sich diese Frage ansieht, einen einzigen Standpunkt für die Frage hat? –

+0

@PeterBons Was lässt Sie denken, dass dies nur eine Teilantwort und keine fertige Antwort ist? – Servy

0

Ich verwende dann eine Parallel.ForEach auf der Liste, um die Aufgaben zu bearbeiten.

Das ist eigentlich ziemlich schlecht.Es wirft einen Ton von Threads in den Mix, nur um die Aufgaben zu blockieren.

Aber wenn die Aufgabe eine Ausnahme auslöst, weiß ich nicht, wie ich sie an die spezifische konkrete Anfrage binden kann, die sie ausgelöst hat. Ich muss es wissen, damit ich dem Anrufer eine korrekt aufgebaute Antwort zurückgeben kann.

Jedes Mal, wenn Sie eine „Prozessaufgabe, nachdem sie vollständig“ haben Art von Problem, in der Regel die beste Lösung ist ein höherer Ebene asynchroner Betrieb:

private async Task<IResponse> ProcessAsync(IRequest request) 
{ 
    try 
    { 
    return await engine.GetResult(request); 
    } 
    catch (Exception ex) 
    { 
    IResponse result = /* create error response */; 
    return result; 
    } 
} 

Dies eine viel einfachere Hauptfunktion erlaubt:

MasterResposne resposne = new MasterResponse(); 

var tasks = MasterRequest.Requests.Select(req => ProcessAsync(req)); 
response.AddRange(await Task.WhenAll(tasks)); 
Verwandte Themen