我有一个WPF应用程序,需要不时执行长时间运行的操作 - 或者说,许多小操作总共需要一段时间。我发现.Net 4中的Task Parallel Library可以很好地处理这个问题。然而,由于本质上这个操作在另一个相同类型的操作开始之前必须完成,因此只能同时运行一个操作。即使上一个操作仍在进行中,用户也有非常真实的机会执行需要该进程运行的操作。我想同步这个过程,以便始终只有一个操作在运行。当正在运行的实例完成后,另一个实例获得锁并继续运行,直到没有更多这样的操作为止。我有一个运行这个过程的类名为EntityUpdater。在这个类中,我认为定义同步对象是聪明的:
使其静态化应确保只要锁定正确,任何EntityUpdater对象都将等待其轮到自己的时间,对吗?
因此,在启动任务之前(这将依次启动所有其他小子任务,并附加到它们的父任务),我进行了天真的第一次尝试:
我知道我应该把这段代码放在finally块中,但它几乎是连续执行的唯一代码,所以我不知道这样做有什么区别。
无论如何,我认为这里涉及到一些线程魔法,导致我收到“从未同步的代码块调用了对象同步方法”SynchronizationLockException异常。我已确保_lockAquired实际上为true,并尝试在几个不同的位置使用Monitor.Enter,但我总是得到这个异常。
因此,我的问题基本上是如何同步访问一个对象(对象本身并不重要),以便在任何给定时间只运行一个进程副本,并且在一个已经运行时启动的任何其他进程都将被阻止?复杂性 - 我猜 - 出现在添加的要求上,即锁定应在将来的某个时候释放,当第一个TPL任务的所有子任务完成时。
更新:
下面是一些代码,展示了我目前正在做的事情。
private static object _lockObject = new object();
使其静态化应确保只要锁定正确,任何EntityUpdater对象都将等待其轮到自己的时间,对吗?
因此,在启动任务之前(这将依次启动所有其他小子任务,并附加到它们的父任务),我进行了天真的第一次尝试:
Monitor.Enter(_lockObject, ref _lockAquired);
(_lockAquired只是一个本地的布尔值)
主任务(带有所有子任务的任务)有一个继续,其存在主要是为了执行以下操作
Monitor.Exit(_lockObject);
我知道我应该把这段代码放在finally块中,但它几乎是连续执行的唯一代码,所以我不知道这样做有什么区别。
无论如何,我认为这里涉及到一些线程魔法,导致我收到“从未同步的代码块调用了对象同步方法”SynchronizationLockException异常。我已确保_lockAquired实际上为true,并尝试在几个不同的位置使用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()调用。
希望这能让事情变得更清晰一些。
lock
代码块不可能同时执行,所以从你写的内容来看似乎是正确的。或许提供更多的代码示例会有帮助。 - faester