Синхронизация с объектом при использовании библиотеки параллельных задач - PullRequest
4 голосов
/ 16 июня 2011

У меня есть приложение WPF, которое время от времени должно выполнять длительные операции, или, скорее, многие небольшие операции, которые в общей сложности занимают время. Я обнаружил, что библиотека параллельных задач в .Net 4 прекрасно работает для этого.

Однако эта операция по своей природе должна завершиться до того, как будет запущена другая аналогичная операция И есть очень реальный шанс, что пользователь сможет выполнить действие, требующее запуска процесса, даже когда последний еще не завершен. Я хотел бы синхронизировать это так, чтобы одновременно работал только один. Когда запущенный экземпляр завершает работу, другой получает блокировки и использует их и т. Д. До тех пор, пока их больше не будет.

У меня есть класс, который запускает процесс с именем EntityUpdater. В этом классе я подумал, что было бы разумно определить объект синхронизации:

private static object _lockObject = new object();

Установка статичности должна гарантировать, что любой объект EntityUpdater будет ожидать своего поворота, пока блокировка правильная, верно?

Итак, моя наивная первая попытка сделала это перед запуском Задачи (которая, в свою очередь, запускает все другие маленькие дочерние задачи, привязанные к их родителю):

Monitor.Enter(_lockObject, ref _lockAquired);

(_ lockAquired - это просто локальный бул)

У главной задачи (у которой есть все дочерние задачи) есть продолжение, которое существует более или менее только для выполнения

Monitor.Exit(_lockObject);

Я знаю, что я должен поставить это в конце, но это в значительной степени единственный код в продолжении, поэтому я не понимаю, как это могло бы изменить ситуацию.

В любом случае, я предполагаю, что здесь есть некое многопоточное вуду, которое заставляет меня получить «Метод синхронизации объектов был вызван из несинхронизированного блока кода» Исключение SynchronizationLockException. Я убедился, что _lockAquired действительно соответствует действительности, и я попытался выполнить Monitor.Enter в нескольких разных местах, но я всегда получаю это.

Итак, в основном, мой вопрос заключается в том, как я могу синхронизировать доступ к объекту (сам объект не важен), чтобы в любой момент времени работала только одна копия процесса, и любые другие, которые могут быть запущены, пока один уже работает будет блокировать? Сложность - я полагаю - появляется с дополнительным требованием, чтобы блокировка была снята в будущем, когда все дочерние задачи первой задачи TPL будут завершены.

UPDATE

Вот код, который показывает, что я сейчас делаю.

public class EntityUpdater
{
    #region Fields

    private static object _lockObject = new object();
    private bool _lockAquired;

    private Stopwatch stopWatch;

    #endregion

    public void RunProcess(IEnumerable<Action<IEntity>> process, IEnumerable<IEntity> entities)
    {
        stopWatch = new Stopwatch();

        var processList = process.ToList();

        Monitor.Enter(_lockObject, ref _lockAquired);

        //stopWatch.Start();

        var task = Task.Factory.StartNew(() => ProcessTask(processList, entities), TaskCreationOptions.LongRunning);
        task.ContinueWith(t =>
        {
            if(_lockAquired)
                Monitor.Exit(_lockObject);
            //stopWatch.Stop();

        });
    }

    private void ProcessTask(List<Action<IEntity>> process, IEnumerable<IEntity> entities)
    {

        foreach (var entity in entities)
        {
            var switcheroo = entity; // To avoid closure or whatever
            Task.Factory.StartNew(() => RunSingleEntityProcess(process, switcheroo), TaskCreationOptions.AttachedToParent);
        }
    }

    private void RunSingleEntityProcess(List<Action<IEntity>> process, IEntity entity)
    {
        foreach (var step in process)
        {
            step(entity);
        }
    }
}

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

Исключение, которое я получаю, конечно же, в вызове Monitor.Exit () в продолжении задачи.

Надеюсь, это прояснит ситуацию.

1 Ответ

3 голосов
/ 16 июня 2011

Вы можете использовать очередь и убедиться, что одновременно выполняется только одна задача, которая будет обрабатывать очередь, например:

private readonly object _syncObj = new object();
private readonly ConcurrentQueue<Action> _tasks = new ConcurrentQueue<Action>();

public void QueueTask(Action task)
{
    _tasks.Enqueue(task);

    Task.Factory.StartNew(ProcessQueue);
}

private void ProcessQueue()
{
    while (_tasks.Count != 0 && Monitor.TryEnter(_syncObj))
    {
        try
        {
            Action action;

            while (_tasks.TryDequeue(out action))
            {
                action();
            }
        }
        finally
        {
            Monitor.Exit(_syncObj);
        }
    }
}
...