Как заблокировать доступ к методу до завершения анимации - PullRequest
2 голосов
/ 15 ноября 2010

У меня есть приложение Silverlight.это имеет базовую анимацию, где прямоугольник анимируется на новую позицию.Анимация состоит из двух DoubleAnimation () - один преобразует X, другой преобразует Y. Он работает нормально.

В основном я хочу заблокировать любые другие вызовы этого метода анимации, пока не завершатся первые две анимации.Я вижу, что класс DoubleAnimation () имеет событие Completed, которое он запускает, но мне не удалось создать какой-либо код, который успешно блокирует, пока оба не завершатся.

Я попытался использовать Monitor.Enter назакрытый член при входе в метод, затем снятии блокировки с одного из событий Completed анимации, но мои попытки связать два события (так что блокировка не снята, пока оба не завершены) не увенчались успехом.

Вот как выглядит метод анимации:

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

    }

EDIT (добавлена ​​дополнительная информация)

Сначала я столкнулся с этим, потому что вызывал этот метод из другого метода (поскольку он обрабатывал элементыон сделал вызов анимации).Я заметил, что предметы не оказались там, где я ожидал.Новые координаты X / Y, которые я передаю, основаны на текущем местоположении элементов, поэтому, если они вызывались несколько раз до завершения, они оказались в неправильном месте.В качестве теста я добавил кнопку, которая запускала анимацию только один раз.Это сработало.Однако, если я нажимаю на кнопку несколько раз подряд, я вижу то же поведение, что и раньше: элементы оказываются в неправильном месте.

Да, похоже, анимации Silverlight запускаются в основном потоке пользовательского интерфейса,В одном из тестов, которые я попробовал, я добавил два свойства, которые отмечали, завершены ли обе анимации.В методе 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 голосов
/ 17 ноября 2010

Этот вопрос действительно заинтересовал меня.На самом деле, я собираюсь включить это в мой следующий пост в блоге.

Сводка вниз, просто чтобы быть уверенным, что мы говорим об одном и том же, радикально, вы не хотите блокировать вызов AnimateRectangle вы просто хотите «поставить в очередь» вызов, чтобы после завершения анимации любой ожидающий вызов выполнялся этот «очередь».Кроме того, вам может потребоваться поставить в очередь несколько вызовов, если предыдущий вызов еще даже не начался.

Итак, нам нужны две вещи: -

  1. Средство для обработки того, что по существу асинхроннооперации (sb.Begin до Завершенного события) как последовательная операция, одна операция начинается только после завершения предыдущей операции.
  2. Средство ставит в очередь дополнительные операции, когда одна или несколько операций еще не завершены.

AsyncOperationService

Элемент 1 в Silverlight представлен различными способами из-за асинхронного характера многих вещей.Я решаю эту проблему с помощью простого асинхронного бегуна, занесенного в блог здесь .Добавьте код 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, который возвращает 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, который ставит операцию вочередь: -

 public void AnimateRectangle(Rectangle rect, double newX, double newY)  
 {
     myAnimationQueue.Enqueue(GetAnimateRectangleOp(rect, newX, newY)
 }
0 голосов
/ 16 ноября 2010

Насколько я знаю, все анимации в Silverlight в любом случае происходят в потоке пользовательского интерфейса. Я предполагаю, что в любом случае только анимация вызывает эту функцию анимации, поэтому я не уверен, что использование блокировки поможет. Вы действительно хотите заблокировать весь поток или просто запретить запуск другой анимации?

Я бы предложил что-то вроде этого:

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

Просто следите за тем, анимируете ли вы в данный момент с помощью флага, и возвращайтесь рано, если это так. Если вы не хотите терять потенциальную анимацию, другой вариант - оставить какую-то очередь для анимации, которую можно проверить / запустить после завершения каждой анимации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...