在动画完成之前,如何阻止对方法的访问

时间:2010-11-15 20:59:23

标签: multithreading silverlight

我有一个Silverlight应用程序。具有基本动画,其中矩形动画到新位置。动画由两个DoubleAnimation()组成 - 一个转换X,另一个转换Y.它工作正常。

我基本上想要阻止对此animate方法的任何其他调用,直到前两个动画完成。我看到DoubleAnimation()类有一个它触发的Completed事件,但是我还没有成功构造任何类型的代码,直到两个代码都完成为止。

我尝试在输入方法时在私有成员上使用Monitor.Enter,然后从其中一个动画完成事件中释放锁定,但我尝试将这两个事件链接起来(因此锁定不会释放,直到两者都有完成)没有成功。

这是动画方法的样子:

    public void AnimateRectangle(Rectangle rect, double newX, double newY)
    {
            var xIsComplete = false;

            Duration duration = new Duration(new TimeSpan(0, 0, 0, 1, 350));
            var easing = new ElasticEase() { EasingMode = EasingMode.EaseOut, Oscillations = 1, Springiness = 4 };
            var animateX = new DoubleAnimation();
            var animateY = new DoubleAnimation();

            animateX.EasingFunction = easing;
            animateX.Duration = duration;
            animateY.EasingFunction = easing;
            animateY.Duration = duration;

            var sb = new Storyboard();

            sb.Duration = duration;
            sb.Children.Add(animateX);
            sb.Children.Add(animateY);

            Storyboard.SetTarget(animateX, rect);
            Storyboard.SetTargetProperty(animateX, new PropertyPath("(Canvas.Left)"));
            Storyboard.SetTarget(animateY, rect);
            Storyboard.SetTargetProperty(animateY, new PropertyPath("(Canvas.Top)"));

            animateX.To = newX;
            animateY.To = newY;
            sb.Begin();

    }

编辑(添加更多信息)

我最初遇到这个因为我从另一个方法调用此方法(因为它处理了调用动画的项目)。我注意到这些物品并没有达到我预期的水平。我传入的新X / Y坐标是基于项目的当前位置,因此如果它在完成之前被多次调用,则它最终位于错误的位置。作为测试,我添加了一个仅运行动画一次的按钮。有效。但是,如果我连续多次点击该按钮,我会看到与之前相同的行为:项目最终位于错误的位置。

是的,似乎Silverlight动画在主UI线程上运行。我试过的其中一个测试我添加了两个标记两个动画是否已完成的属性。在AnimateRectange()方法中,我在while循环中调用它们(调用Thread.Sleep)。这个循环从未完成(所以它肯定在同一个线程上)。

所以我创建了一个队列来按顺序处理动画:

    private void ProcessAnimationQueue()
    {
        var items = this.m_animationQueue.GetEnumerator();
        while (items.MoveNext())
        {
            while (this.m_isXanimationInProgress || this.m_isYanimationInProgress)
            {
                System.Threading.Thread.Sleep(100);
            }

            var item = items.Current;
            Dispatcher.BeginInvoke(() => this.AnimateRectangle(item.Rect.Rect, item.X, item.Y));                
        }
    }

然后我调用我的初始例程(将动画排队)并在新线程上调用此方法。我看到了相同的结果。

2 个答案:

答案 0 :(得分:0)

据我所知,Silverlight中的所有动画都是在UI线程上发生的。我猜测只有UI线程无论如何都在调用这个动画函数,所以我不确定使用锁定会有所帮助。你真的想要阻止整个线程或阻止另一个动画启动吗?

我会建议更像这样的事情:

private bool isAnimating = false;

public void AnimateRectangle(Rectangle rect, double newX, double newY)
{
    if (isAnimating)
        return;

    // rest of animation code

    sb.Completed += (sender, e) =>
    {
        isAnimating = false;
    };

    isAnimating = true;
    sb.Begin();
}

只需跟踪您当前是否正在制作带有标记的动画,如果有,请尽早返回。如果你不想丢失潜在的动画,你的另一个选择就是保留某种动画队列,你可以在每个动画完成时检查/启动它。

答案 1 :(得分:0)

这个问题确实引起了我的兴趣。事实上,我将把它包含在我的下一篇博文中。

将其归结,只是为了确保我们正在谈论同样的事情,但是你不想阻止对AnimateRectangle的呼叫你只想“排队”呼叫以便一旦有任何未完成的呼叫已完成动画,此“排队”调用将被执行。如果先前的呼叫尚未开始,您可能需要对多个呼叫进行排队。

所以我们需要两件事: -

  1. 将基本上异步操作(sb.Begin到完成事件)作为顺序操作处理的方法,一个操作仅在上一个操作完成时开始。
  2. 在一个或多个操作尚未完成时排队其他操作的方法。
  3. <强> AsyncOperationService

    由于许多事情的异步特性,第1项在Silverlight中以不同的方式出现。我通过一个简单的异步操作转换器博客here解决了这个问题。将AsyncOperationService代码添加到项目中。

    <强> AsyncOperationQueue

    它的第2项确实引起了我的兴趣。这里的变化是,当现有的一组操作正在进行时,需要添加另一个。对于一般情况解决方案,我们需要一种包含其他操作的线程安全方法。

    以下是AsyncOperationQueue

    的简单内容
    public class AsyncOperationQueue
    {
        readonly Queue<AsyncOperation> myQueue = new Queue<AsyncOperation>();
        AsyncOperation myCurrentOp = null;
    
        public void Enqueue(AsyncOperation op)
        {
            bool start = false;
    
            lock (myQueue)
            {
                if (myCurrentOp != null)
                {
                    myQueue.Enqueue(op);
                }
                else
                {
                    myCurrentOp = op;
                    start = true;
                }
            }
    
            if (start)
                DequeueOps().Run(delegate { });
        }
    
        private AsyncOperation GetNextOperation()
        {
            lock (myQueue)
            {
                myCurrentOp = (myQueue.Count > 0) ? myQueue.Dequeue() : null;
                return myCurrentOp;
            }
        }
    
        private IEnumerable<AsyncOperation> DequeueOps()
        {
            AsyncOperation nextOp = myCurrentOp;
            while (nextOp != null)
            {
                yield return nextOp;
                nextOp = GetNextOperation();
            }
        }
    }
    

    投入使用

    首先要做的是将现有的AnimateRectangle方法转换为返回GetAnimateRectangleOp的{​​{1}}。像这样: -

    AsyncOperation

    我们需要保存 public AsyncOperation GetAnimateRectangleOp(Rectangle rect, double newX, double newY) { return (completed) => { // Code identical to the body of your original AnimateRectangle method. sb.Begin(); sb.Completed += (s, args) => completed(null); }; } 的实例: -

    AsyncOperationQueue

    最后,我们需要重新创建将队列操作排入队列的 private AsyncOperationQueue myAnimationQueue = new AsyncOperationQueue(); : -

    AnimateRectangle