2011-01-13 8 views
2

ich viel über die neue Task-Funktionalität in .NET 4.0 gelesen haben, aber ich habe keine Lösung für das folgende Problem gefunden:.net 4.0 Aufgaben: Synchronisieren auf ein oder mehrere Objekte

Ich schreibe ein Server-Anwendung, die Anfragen von vielen Benutzern verarbeitet und ich möchte Aufgaben verwenden, um diese Anfrage auf mehrere Kerne zu verteilen. Diese Aufgaben sollten jedoch auf Objekte synchronisiert werden - für den Anfang, Benutzer -, so dass nur eine Aufgabe für jedes Objekt gleichzeitig verarbeitet wird. Dies wäre mit Task.ContinueWith() einfach zu erreichen, aber es sollte auch möglich sein, eine Aufgabe für mehrere Objekte zu synchronisieren (z. B. wenn ein Benutzer Geld an einen anderen Benutzer überträgt, sollte eine Variable bei Benutzer A dekrementiert und bei Benutzer B erhöht werden) ohne andere Aufgaben zu stören).

Also ist mein erster Versuch eine Klasse, die Delegaten empfängt, Aufgaben erstellt und sie in einem Wörterbuch mit den Objekten speichert, auf denen die Schlüssel synchronisiert werden sollen. Wenn eine neue Aufgabe geplant ist, kann sie mit Task.ContinueWith() an die letzte Aufgabe des gegebenen Objekts angehängt werden. Wenn es für mehrere Objekte synchronisiert werden soll, wird die neue Aufgabe mit TaskFactory.ContinueWhenAll() erstellt. Die erstellte Aufgabe wird für jedes Objekt, mit dem sie synchronisiert wird, im Wörterbuch gespeichert. Hier ist mein erster Entwurf:

public class ActionScheduler:IActionScheduler 
{ 
    private readonly IDictionary<object, Task> mSchedulingDictionary = new Dictionary<object, Task>(); 
    private readonly TaskFactory mTaskFactory = new TaskFactory(); 

    /// <summary> 
    /// Schedules actions synchonized on one or more objects. Only one action will be processed for each object at any time. 
    /// </summary> 
    /// <param name="synchronisationObjects">Array of objects the current action is synchronized on</param> 
    /// <param name="action">The action that will be scheduled and processed</param> 
    public void ScheduleTask(object[] synchronisationObjects, Action action) 
    {    
     // lock the dictionary in case two actions are scheduled on the same object at the same time 
     // this is necessary since reading and writing to a dictionary can not be done in an atomic manner 
     lock(mSchedulingDictionary) 
     { 
      // get all current tasks for the given synchronisation objects 
      var oldTaskList = new List<Task>(); 
      foreach (var syncObject in synchronisationObjects) 
      { 
       Task task; 
       mSchedulingDictionary.TryGetValue(syncObject, out task); 
       if (task != null) 
        oldTaskList.Add(task); 
      } 

      // create a new task for the given action 
      Task newTask; 
      if (oldTaskList.Count > 1) 
      { 
       // task depends on multiple previous tasks 
       newTask = mTaskFactory.ContinueWhenAll(oldTaskList.ToArray(), t => action()); 
      } 
      else 
      { 
       if (oldTaskList.Count == 1) 
       { 
        // task depends on exactly one previous task 
        newTask = oldTaskList[0].ContinueWith(t => action()); 
       } 
       else 
       { 
        // task does not depend on any previous task and can be started immediately 
        newTask = new Task(action); 
        newTask.Start(); 
       } 
      } 

      // store the task in the dictionary 
      foreach (var syncObject in synchronisationObjects) 
      { 
       mSchedulingDictionary[syncObject] = newTask; 
      } 
     } 
    } 
} 

Dies funktioniert auch, wenn eine Aufgabe „multiSyncTask“ für mehrere Objekte erstellt wurde, und danach Aufgaben für jedes der Objekte sind geplant. Da sie alle mit multiSyncTask.ContinueWith() erstellt werden, beginnen sie synchron:

static void Main() 
    { 
     IActionScheduler actionScheduler = new ActionScheduler(); 

     var syncObj1 = new object(); 
     var syncObj2 = new object(); 

     // these two start and complete simultaneously: 
     actionScheduler.ScheduleTask(new[] { syncObj1 },() => PrintTextAfterWait("1")); 
     actionScheduler.ScheduleTask(new[] { syncObj2 },() => PrintTextAfterWait("2")); 
     // this task starts after the first two and "locks" both objects: 
     actionScheduler.ScheduleTask(new[] { syncObj1, syncObj2 },() => PrintTextAfterWait("1 and 2")); 
     // these two - again - start and complete simultaneously after the task above: 
     actionScheduler.ScheduleTask(new[] { syncObj1 },() => PrintTextAfterWait("1")); 
     actionScheduler.ScheduleTask(new[] { syncObj2 },() => PrintTextAfterWait("2")); 
    } 

    static void PrintTextAfterWait(string text) 
    { 
     Thread.Sleep(3000); 
     Console.WriteLine(text); 
    } 

Was denken Sie - ist dies eine gute Lösung für mein Problem? Ich bin ein wenig skeptisch gegenüber dem großen Schloss des Wörterbuchs, aber es ist notwendig, wenn zwei Aufgaben auf einem Objekt gleichzeitig geplant sind, um Rennbedingungen zu verhindern. Natürlich ist das Wörterbuch nur für die Zeit gesperrt, die zum Erstellen einer Aufgabe benötigt wird, nicht für die Verarbeitung.

Außerdem würde ich gerne wissen, ob es bereits vorhandene Lösungen oder Codierungs-Paradigmen gibt, die mein Problem mit .net 4.0 Aufgaben besser lösen, die ich nicht ausfindig machen konnte.

Vielen Dank und mit freundlichen Grüßen, Johannes

+0

Ja, nicht sicher über das Schloss; Es umschließt zwei For-Each-Loops. – IAbstract

+0

Genau, aber in diesen Schleifen greife ich auf das Wörterbuch zu, also muss ich es unter Verschluss halten. Die Alternative wäre, nur die Elemente zu sperren, auf die ich zugreifen möchte, aber dies kann zu Deadlocks führen, wenn die erste Anfrage an Objekt A und B sperren soll, und die zweite an B und A ... Ein weiteres Problem ist das Ich muss Objekte manuell aus dem Wörterbuch entfernen, damit sie Müll sammeln können ... – Johannes

Antwort

0

Wenn ich Sie richtig verstanden habe .. Sie Task.ContinueWith (task1, task2, lambda) haben möchte? So etwas wie der Join-Arbiter in CCR? http://msdn.microsoft.com/en-us/library/bb648749.aspx Wenn ja, ist wahrscheinlich die eleganteste Option, den JoinBlock im TPL-Datenfluss zu verwenden (http://www.microsoft.com/download/en/confirmation.aspx?id=14782). Oder haben Sie vielleicht versucht, Task.WaitAll() als erste Anweisung Ihrer abhängigen Aufgabe zu verwenden?

Verwandte Themen