Задачи .net 4.0: синхронизация на одном или нескольких объектах - PullRequest
2 голосов
/ 13 января 2011

Я много читал о новой функциональности Задачи в .net 4.0, но я не нашел решения для следующей проблемы:

Я пишу серверное приложение, которое обрабатывает запросы от многих пользователей иЯ хочу использовать Задачи для распределения этих запросов по нескольким ядрам.Тем не менее, эти задачи должны быть синхронизированы на объектах - для начала, на пользователях - так, чтобы одновременно обрабатывалось только одно задание для каждого объекта.Это было бы просто сделать с помощью Task.ContinueWith (), но также должна быть возможность синхронизировать задачу для нескольких объектов (например, когда пользователь переводит деньги другому пользователю, переменная должна быть уменьшена для пользователя A и увеличена для пользователя B).без вмешательства других задач).

Итак, моя первая попытка - это класс, который получает делегаты, создает задачи и сохраняет их в словаре с объектами для синхронизации в качестве ключей.Если новая задача запланирована, она может быть добавлена ​​к последней задаче данного объекта с помощью Task.ContinueWith ().Если он должен быть синхронизирован на нескольких объектах, новая задача создается с помощью TaskFactory.ContinueWhenAll ().Созданная задача сохраняется в словаре для каждого объекта, с которым она синхронизируется.Вот мой первый черновик:

 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;
            }
        }
    }
}

Это работает даже в том случае, если задача «multiSyncTask» была создана для нескольких объектов, а затем запланированы задачи для каждого из объектов.Поскольку все они созданы с помощью multiSyncTask.ContinueWith (), они запускаются синхронно:

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);
    }

Как вы думаете - это хорошее решение для моей проблемы?Я немного скептически отношусь к большой блокировке словаря, но это необходимо в случае, если две задачи запланированы на одном объекте одновременно, чтобы предотвратить условия гонки.Конечно, словарь просто заблокирован на время, необходимое для создания задачи, а не на то, когда она обрабатывается.

Кроме того, я хотел бы знать, существуют ли какие-либо уже существующие решения или парадигмы кодирования, которыеРешите мою проблему лучше, используя .net 4.0 Задачи, которые мне не удалось отследить.

Спасибо и с наилучшими пожеланиями, Йоханнес

1 Ответ

0 голосов
/ 26 июля 2011

Если я вас правильно понял .. вы хотели бы иметь Task.ContinueWith (task1, task2, lambda)? Что-то вроде арбитра Join в CCR? http://msdn.microsoft.com/en-us/library/bb648749.aspx Если это так, вероятно, самый элегантный вариант - это использовать JoinBlock в потоке данных TPL (http://www.microsoft.com/download/en/confirmation.aspx?id=14782). Или, может быть, вы пытались использовать Task.WaitAll () в качестве первой инструкции вашей зависимой задачи?

...