使用任务并行库时与对象同步
本文关键字:对象 同步 任务 并行 | 更新日期: 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);
}
}
}