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
Ja, nicht sicher über das Schloss; Es umschließt zwei For-Each-Loops. – IAbstract
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