使用任务并行库时与对象同步

时间:2011-06-15 20:09:30

标签: c# multithreading .net-4.0 task-parallel-library

我有一个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任务的所有子任务完成后,在未来的某个时间释放锁的额外需求出现了。

更新

以下是一些显示我现在正在做的事情的代码。

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 个答案:

答案 0 :(得分:3)

您可以使用队列并确保在处理队列的时间内只执行单个任务,例如:

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