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

本文关键字:对象 同步 任务 并行 | 更新日期: 2023-09-27 17:59:10

我有一个WPF应用程序,它不时需要执行长时间运行的操作,或者更确切地说,许多小操作总共需要一段时间。我在中发现了任务并行库。Net 4对此运行良好。

然而,这个操作本质上应该在另一个同类操作开始之前运行到完成。用户很有可能执行需要运行进程的操作,即使最后一个操作仍在进行。我想同步这个,这样一次只运行一个。当正在运行的实例完成时,另一个实例获得锁并进行锁定,等等,直到没有更多的锁可以运行为止。

我有一个类,它运行名为EntityUpdater的进程。在这节课上,我认为定义一个同步化对象会很聪明:

private static object _lockObject = new object();

使其为静态应确保只要锁定正确,任何EntityUpdater对象都将等待轮到它,对吧?

因此,我天真的第一次尝试是在开始任务之前这样做的(任务依次启动所有其他小孩的任务,附在他们的父母身上):

Monitor.Enter(_lockObject, ref _lockAquired);

(_lockAquired只是本地布尔)

主任务(包含所有子任务的任务)有一个延续,它或多或少只是为了完成而存在

Monitor.Exit(_lockObject);

我知道我应该把它放在finally中,但它几乎是延续中唯一的代码,所以我不认为这会有什么不同。

无论如何,我假设这里有一些线程化的voodoo导致我得到"对象同步方法是从未同步的代码块调用的"SynchronizationLockException。我已经确定_lockAquired实际上是真的,并且我已经尝试过Monitor。进入几个不同的地方,但我总是得到这个。

所以,基本上,我的问题是如何同步对对象的访问(对象本身并不重要),以便在任何给定时间只有一个进程副本在运行,而在一个进程已经运行时可能启动的任何其他副本都会被阻止?我想,当第一个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);
        }
    }
}

正如你所看到的,这并不复杂,而且这可能也远远不值得制作——只是一个展示我无法工作的尝试。

我得到的例外当然在监视器中。任务继续中的Exit()调用。

我希望这能让事情变得更清楚一点。

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

您可以使用队列并确保一次只执行一个任务来处理队列,例如:

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