我有一个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()调用中。
我希望能让这一点更加清晰。
答案 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);
}
}
}