【发布时间】:2011-06-16 04:56:14
【问题描述】:
我有一个 WPF 应用程序,它不时需要执行长时间运行的操作 - 或者更确切地说,许多小操作总共需要一段时间。我发现 .Net 4 中的任务并行库可以很好地解决这个问题。
但是,这个操作本质上应该在另一个同类操作开始之前完成。即使最后一个仍在运行,用户也很有可能执行需要该进程运行的操作。我想同步它,以便一次只运行一个。当正在运行的实例完成时,另一个实例获得锁并继续执行,等等,直到没有更多的实例可以运行。
我有一个运行名为 EntityUpdater 的进程的类。在这个类中,我认为定义一个同步对象会很聪明:
private static object _lockObject = new object();
将其设为静态应确保只要锁定正确,任何 EntityUpdater 对象都将等待轮到它,对吧?
所以我幼稚的第一次尝试是在开始任务之前执行此操作(依次启动所有其他小子任务,附加到它们的父任务):
Monitor.Enter(_lockObject, ref _lockAquired);
(_lockAquired 只是一个本地布尔值)
主要任务(包含所有子任务的任务)有一个延续,它或多或少只是为了做而存在
Monitor.Exit(_lockObject);
我知道我应该把它放在 finally 中,但它几乎是延续中唯一的代码,所以我不明白这会有什么不同。
无论如何,我假设这里有一些线程巫术导致我得到“对象同步方法是从不同步的代码块调用的”SynchronizationLockException。我已经确定 _lockAquired 实际上是真的,并且我尝试在几个不同的地方进行 Monitor.Enter,但我总是得到这个。
所以,基本上,我的问题是如何同步对对象的访问(对象本身并不重要),以便在任何给定时间只有一个进程副本正在运行,而其他任何可能在一个已经运行会阻塞?当第一个 TPL 任务的所有子任务都完成时,我猜想,当第一个 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() 调用中。
我希望这会让这更清楚一点。
【问题讨论】:
-
很抱歉,您能不能稍微清除一下。在我的印象中,没有两个
locked 使用同一个对象的代码块可以同时执行,所以从你写的一切听起来都是正确的。也许更多的代码示例会有所帮助。 -
嗨,当然,我会更新一些源代码来展示我在做什么。
-
ContinueWith 方法不是线程安全的,而且不必要地复杂。考虑一下 Parallel.ForEach() 周围的 try/finally。
-
@Henk - 好吧,我会尝试的 - 但是,我的理解是,在这种情况下的延续将在所有子任务完成后运行 - 只要它除了实际释放之外几乎没有锁,会不会有问题?我主要关心的是使调用者易于调用和非阻塞。谢谢!
-
不是一个完整的答案,但通过阻止获取锁来启动每个任务时要非常小心。如果你这样做的次数足够多,你可能会饿死线程池,并且可能会发生各种不好的事情(例如,你的延续可能无法运行,因为它们无法被调度,因为其他任务已经启动并进入阻塞状态。 ) 如果您在继续执行之前不释放,那么您现在处于死锁状态。
标签: c# multithreading .net-4.0 task-parallel-library