简体   繁体   English

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

[英]Synchronizing with an object while using the Task Parallel Library

I have a WPF app that from time to time needs to perform a long-running operation - or rather, many small operations that in sum total take a while. 我有一个WPF应用程序,不时需要执行长时间运行 - 或者更确切地说,许多小型操作总共需要一段时间。 I have found that the Task Parallel Library in .Net 4 works fine for this. 我发现.Net 4中的任务并行库可以正常工作。

However, this operation by nature should run to completion before another of the same kind is started. 但是,这种操作本质上应该在另一种相同类型开始之前完成。 And there is a very real chance that the user could perform an action that requires the process to run, even while the last one is still going. 并且用户可以执行需要进程运行的操作,即使在最后一个仍在运行时也是如此。 I would like to synchronize this so that only one is ever running at a time. 我想同步这个,这样一次只能运行一个。 When the running instance completes, another gains the locks and goes for it, etc. until there are no more of these to run. 当正在运行的实例完成时,另一个获取锁定并为其进行操作,等等,直到不再运行这些锁定为止。

I have a class that runs the process named EntityUpdater. 我有一个运行名为EntityUpdater的进程的类。 In this class I thought it would be clever to define a syncronization object: 在这个类中,我认为定义同步对象会很聪明:

private static object _lockObject = new object();

Making it static should ensure that any EntityUpdater object will await its turn as long as the locking is correct, right? 将其设置为静态应该确保只要锁定正确,任何EntityUpdater对象都会等待它,对吧?

So my naive first attempt did this before starting the Task (which in turns starts all the other little child tasks, attached to their parent): 所以我的天真的第一次尝试在启动任务之前做了这个(它依次启动所有其他小孩子任务,附加到他们的父母):

Monitor.Enter(_lockObject, ref _lockAquired);

(_lockAquired is just a local bool) (_lockAquired只是一个本地bool)

The main task (the one with all the child tasks) has a continuation, which exists more or less only to do 主要任务(具有所有子任务的任务)具有延续,其或多或少仅存在

Monitor.Exit(_lockObject);

I know I should put this in a finally, but it is pretty much the only code in the continuation, so I don't see how that would make a difference. 我知道我应该把它放在最后,但它几乎是延续中唯一的代码,所以我不知道这会产生什么影响。

Anyway, I assume that there is some threading voodoo here that causes me to get the "Object synchronization method was called from an unsynchronized block of code" SynchronizationLockException. 无论如何,我认为这里存在一些线程伏都教导致我得到“从非同步代码块调用对象同步方法”SynchronizationLockException。 I have made sure that _lockAquired is actually true, and I have tried to Monitor.Enter in several different places, but I always get this. 我已经确定_lockAquired实际上是真的,我已经尝试过Monitor.Enter在几个不同的地方,但我总是得到这个。

So, basically, my question is how I can synchronize access to an object (the object itself is not important) so that only one copy of the process is running at any given time, and any others that may be started while one is already running will block? 所以,基本上,我的问题是我如何同步对象的访问(对象本身并不重要),以便在任何给定时间只运行一个进程副本,以及在一个已经运行时可能启动的任何其他副本会阻止吗? The complication - I guess - appears with the added demand that the lock should be released at some time in the future, when all the child tasks of the first TPL Task are complete. 复杂性 - 我猜 - 出现了增加的需求,即当第一个TPL任务的所有子任务完成时,锁定应该在将来的某个时间释放。

UPDATE UPDATE

Here is some code that shows what I am doing right now. 这是一些代码,显示我现在正在做什么。

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

As you can see, it is not complicated, and this is also probably far from production worthy - just an attempt that shows what I can't get to work. 正如你所看到的,它并不复杂,这也可能远远不是生产值得的 - 只是一个尝试,显示我无法工作。

The exception I get is of course in the Monitor.Exit() call in the task continuation. 我得到的例外当然是在任务延续中的Monitor.Exit()调用中。

I hope that makes this a bit clearer. 我希望这会让这一点更加清晰。

You could use a queue and ensure that only a single task is executing at a time which will process the queue, eg: 您可以使用队列并确保在处理队列的时间内只执行一个任务,例如:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM