【问题标题】:Synchronizing with an object while using the Task Parallel Library在使用任务并行库时与对象同步
【发布时间】: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


【解决方案1】:

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

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

【讨论】:

  • 您好,感谢您的建议。这当然是一种可能性——但这意味着我必须跟踪这个类来保持状态,对吧?这是一种可能性,但我试图避免这种情况。不过,我会记住它作为一种可能的解决方案!
  • @Rune 最好将这样的功能分离到它们自己的类中。这是一种被称为“单一职责原则”的软件工程实践。简而言之,它是关于使您的软件更加模块化并且更易于理解和维护。希望这会有所帮助。
  • 嗨,是的,我知道原理 - 因此 EntityUpdater 是一个单独的类,只做这个。但是,我的观点是,我基本上希望我的代码的任何部分都新建一个 EntityUpdater 并启动一个进程,而不是必须找到具有整个系统的 ConcurrentQueue 的单个“全局”EntityUpdater。如果可能,我希望将全局变量保持在最低限度。 :)
  • @Rune 你可能也知道依赖注入。
  • @chibacity - 当然,我也在这个应用程序中使用它(在这种情况下是“穷人的 DI”,因为这个应用程序没有非常复杂的需求)。但是,我不希望“永远”保持单例,而是让 EntityUpdaters 是短暂的、瞬态的实体,所以同样适用于 wrt。将其保留为全局对象。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多