0

У меня такая проблема, что в таймере предыдущая обработка еще не выполнилась, а я новая уже началась.

public virtual void OnTick()
{
    if (!Entity.IsSpawned) return;

    if (IsDead) return;

    if (CooldownTick > 0)
    {
        CooldownTick--;
    }


    if (IsInvulnerable) Health = MaxHealth;

    if (Health <= 0)
    {
        Kill();
        return;
    }

    if (Entity.KnownPosition.Y < 0 && !IsDead)
    {
        lock (Locked)
        {
            CooldownTick = 0;
            TakeHit(null, 300, DamageCause.Void);
            return;
        }
    }

    if (IsInWater(Entity.KnownPosition))
    {
        Entity.IsInWater = true;

        Air--;
        if (Air <= 0)
        {
            if (Math.Abs(Air)%10 == 0)
            {
                TakeHit(null, 1, DamageCause.Drowning);
                Entity.BroadcastSetEntityData();
            }
        }

        Entity.BroadcastSetEntityData();
    }
    else
    {
        Air = MaxAir;

        if (Entity.IsInWater)
        {
            Entity.IsInWater = false;
            Entity.BroadcastSetEntityData();
        }
    }

    if (IsOnFire && (Entity.IsInWater || IsStandingInWater(Entity.KnownPosition)))
    {
        IsOnFire = false;
        FireTick = 0;
        Entity.BroadcastSetEntityData();
    }

    if (IsInOpaque(Entity.KnownPosition))
    {
        if (SuffocationTicks <= 0)
        {
            TakeHit(null, 1, DamageCause.Suffocation);
            Entity.BroadcastSetEntityData();

            SuffocationTicks = 10;
        }
        else
        {
            SuffocationTicks--;
        }
    }
    else
    {
        SuffocationTicks = 10;
    }

    if (IsInLava(Entity.KnownPosition))
    {
        if (LastDamageCause.Equals(DamageCause.Lava))
        {
            FireTick += 2;
        }
        else
        {
            Ignite(300);
        }

        if (LavaTicks <= 0)
        {
            TakeHit(null, 4, DamageCause.Lava);
            Entity.BroadcastSetEntityData();

            LavaTicks = 10;
        }
        else
        {
            LavaTicks--;
        }
    }
    else
    {
        LavaTicks = 0;
    }

    if (!IsInLava(Entity.KnownPosition) && IsOnFire)
    {
        FireTick--;
        if (FireTick <= 0)
        {
            IsOnFire = false;
            Entity.BroadcastSetEntityData();
        }

        if (FireTick%20 == 0)
        {
            var player = Entity as Player;
            if (player != null)
            {
                player.DamageCalculator.CalculatePlayerDamage(null, player, null, 1, DamageCause.FireTick);
                TakeHit(null, 1, DamageCause.FireTick);
            }
            else
            {
                TakeHit(null, 1, DamageCause.FireTick);
            }
            Entity.BroadcastSetEntityData();
        }
    }
}
  • Приложите ваш код, без него никак ;) – morphey83 Feb 02 '19 at 19:40
  • У меня походу RC – Kirill Meøw Feb 02 '19 at 19:41
  • @KirillMeøw, сработал таймер первый раз, начал работать метод. СРаботал таймер второй раз, метод ещё выполняется, нужно ли ставить в очередь на выполнение этот метод или пропускаем и ждём следующего срабатывания таймера? – Casper Feb 02 '19 at 20:10
  • https://ru.stackoverflow.com/q/516442/10105 – VladD Feb 03 '19 at 01:11

2 Answers2

2

Вот набросал класс, работающий в двух режимах, в одном режиме ставим в очередь метод столько раз, сколько вызвали метод Signal, второй режим будет ставить в очередь метод один раз и игнорировать все вызовы, пока выполняется работа метода.

Если нужно, чтобы метод срабатывал в UI потоке, то экземпляр класса MethodCallLimiter должен быть создан в UI потоке, и в конструктор последним параметром (captureSynchronizationContext) должно быть передано значение true, чтобы был захвачен контекст синхронизации UI потока.

MethodCallLimiter.cs

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Utils
{
    public class MethodCallLimiter
    {
        private readonly Action _action;
        private readonly MethodCallLimiterMode _mode;

        private readonly object _methodCallSync = new object();
        private readonly Queue<Action> _actions = new Queue<Action>();
        private bool _queueProcessorStarted;

        private readonly SynchronizationContext _synchronizationContext;

        public MethodCallLimiter(Action action, MethodCallLimiterMode mode, bool captureSynchronizationContext = false)
        {
            if (captureSynchronizationContext)
            {
                _synchronizationContext = SynchronizationContext.Current ?? new SynchronizationContext();
            }

            _action = action;
            _mode = mode;
        }

        public void Signal()
        {
            lock (_methodCallSync)
            {
                if (_mode == MethodCallLimiterMode.ManyToOne)
                {
                    if (_queueProcessorStarted)
                    {
                        return;
                    }

                    _actions.Enqueue(_action);
                }
                else if (_mode == MethodCallLimiterMode.OneToOne)
                {
                    _actions.Enqueue(_action);

                    if (_queueProcessorStarted)
                    {
                        return;
                    }
                }

                _queueProcessorStarted = true;
            }

            ProcessQueueAsync().LogErrors(nameof(Signal));
        }

        private async Task ProcessQueueAsync()
        {
            while (true)
            {
                Action action;
                lock (_methodCallSync)
                {
                    if (_actions.Count > 0)
                    {
                        action = _actions.Dequeue();
                    }
                    else
                    {
                        _queueProcessorStarted = false;
                        break;
                    }
                }

                await Task.Run(() =>
                {
                    if (_synchronizationContext != null)
                    {
                        _synchronizationContext.Send((unused) =>
                        {
                            action();
                        }, null);
                    }
                    else
                    {
                        action();
                    }
                });
            }
        }
    }
}

MethodCallLimiterMode.cs

namespace Utils
{
    /// <summary>
    /// Режим, в котором будет добавляться вызов метода в очередь.
    /// </summary>
    public enum MethodCallLimiterMode
    {
        OneToOne,
        ManyToOne
    }
}

Program.cs

using System;
using System.Threading;

namespace Utils
{
    class Program
    {
        #region Entry point

        private static Program _program;

        static void Main(string[] args)
        {
            _program = new Program();
            _program.Run(args);
        }

        #endregion

        private MethodCallLimiter _limiter;
        private Timer _timer;

        private int _signalCounter;
        private int _workCounter;

        private void Run(string[] args)
        {
            _limiter = new MethodCallLimiter(DoWork, MethodCallLimiterMode.ManyToOne, false);
            _timer = new Timer(OnTick, null, 0, 1000);

            Console.ReadKey(true);
            _timer.Dispose();
        }

        private void OnTick(object unused)
        {
            int value = Interlocked.Increment(ref _signalCounter);
            Console.WriteLine($"{value:D5}. ---------- Signal ---------- " + nameof(OnTick));
            _limiter.Signal();
        }

        private void DoWork()
        {
            int value = Interlocked.Increment(ref _workCounter);
            Console.WriteLine($"{value:D5}. Begin {nameof(DoWork)}");

            // Имитируем долгую работу
            Thread.Sleep(3000);
            Console.WriteLine($"{value:D5}. End {nameof(DoWork)}");
        }
    }
}

TaskExtensions.cs

using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace Utils
{
    public static class TaskExtensions
    {
        //public static async void LogErrors(this Task task, ILogger logger, string callerMethodName = null)
        //{
        //    try
        //    {
        //        await task;
        //    }
        //    catch (Exception ex)
        //    {
        //        logger.Error((callerMethodName ?? nameof(LogErrors)) + ".", ex);
        //    }
        //}

        public static async void LogErrors(this Task task, string callerMethodName = null)
        {
            try
            {
                await task;
            }
            catch (Exception ex)
            {
                Debug.WriteLine((callerMethodName ?? nameof(LogErrors)) + ". " + ex);
            }
        }
    }
}
Casper
  • 1,228
  • 9
  • 19
  • Круто, чё. Только сложно как-то. У меня пару вопросов: 1) зачем в классе Program сделан такой Entry point? 2) почему не использовали ConcurrentQueue<T>? – Bulson Feb 02 '19 at 20:50
  • @Bulson, на самом деле сложного ничего нет. Копируем MethodCallLimiter и MethodCallLimiterMode, а останое просто для демонстрации. И используем как в примере. Плюс этого в том, что не нужно заморачиваться с остановкой и продолжением работы таймера, можно использовать где угодно и сколько угодно раз. 1) Entry point просто, чтобы не плодить бесконечные static методы, что меня напрягает. 2) Нужно лочить большие куски кода, смысла не вижу ещё и использовать ConcurrentQueue<T>, когда всё равно доступ к очереди блокируется для одного потока. – Casper Feb 02 '19 at 20:55
  • Ещё по вопросу 2). Там не получится просто использовать такую очередь. Нужно, чтобы два разных потока в один момент времени не могли совершать определённые действия, это добавление в очередь и установка переменной true, и извлечение из очереди и установка переменной false. При том оно должно лочиться в паре, нельзя залочить только очередь. – Casper Feb 02 '19 at 21:00
  • Понятно, спасибо. – Bulson Feb 02 '19 at 21:02
0

Добавьте в начало метода

_timer.Stop();

а в конце метода, сразу после последнего if()

_timer.Start();

_timer - это таймер, не знаю как он у вас называется.

Bulson
  • 9,411