Отложенные вызовы функций



есть ли хороший простой метод задержки вызова функции, позволяя потоку продолжать выполнение?



например



public void foo()
{
// Do stuff!

// Delayed call to bar() after x number of ms

// Do more Stuff
}

public void bar()
{
// Only execute once foo has finished
}


Я знаю, что это может быть достигнуто с помощью таймера и обработчиков событий, но мне было интересно, есть ли стандартный способ c# для достижения этого?



Если кому-то интересно, причина, по которой это требуется, заключается в том, что foo() и bar() находятся в разных (одноэлементных) классах, которые мне нужно называть друг другом в исключительных случаях обстоятельства. Проблема в том, что это делается при инициализации, поэтому foo должен вызвать bar, которому нужен экземпляр класса foo, который создается... следовательно, отложенный вызов bar (), чтобы гарантировать, что foo полностью инстанцирован.. Чтение этого назад почти попахивает плохим дизайном !



EDIT



Я возьму пункты о плохом дизайне под совет! Я давно думал, что я мог бы улучшить систему, однако, это противно ситуация только происходит, когда возникает исключение, во всех других случаях два синглета сосуществуют очень хорошо. Я думаю, что я не собираюсь возиться с неприятными асинхронными паттерами, скорее я собираюсь рефакторинг инициализации одного из классов.

723   12  

12 ответов:

благодаря современному C# 5/6:)

public void foo()
{
    Task.Delay(1000).ContinueWith(t=> bar());
}

public void bar()
{
    // do stuff
}

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

public void foo()
{
    System.Threading.Timer timer = null; 
    timer = new System.Threading.Timer((obj) =>
                    {
                        bar();
                        timer.Dispose();
                    }, 
                null, 1000, System.Threading.Timeout.Infinite);
}

public void bar()
{
    // do stuff
}

(спасибо Фред Deschenes для идеи размещения таймера в обратном вызове)

помимо согласия с проектными наблюдениями предыдущих комментаторов, ни одно из решений не было достаточно чистым для меня. .Net 4 предоставляет Dispatcher и Task классы, которые делают отсрочки исполнения в текущем потоке довольно проста:

static class AsyncUtils
{
    static public void DelayCall(int msec, Action fn)
    {
        // Grab the dispatcher from the current executing thread
        Dispatcher d = Dispatcher.CurrentDispatcher;

        // Tasks execute in a thread pool thread
        new Task (() => {
            System.Threading.Thread.Sleep (msec);   // delay

            // use the dispatcher to asynchronously invoke the action 
            // back on the original thread
            d.BeginInvoke (fn);                     
        }).Start ();
    }
}

для контекста, я использую это, чтобы развенчать ICommand привязан к левой кнопке мыши на элементе пользовательского интерфейса. Пользователи дважды щелкают, что вызывает все виды хаоса. (Я знаю, что я мог бы также использовать Click/DoubleClick обработчики, но я хотел решение, которое работает с ICommands по всем направлениям).

public void Execute(object parameter)
{
    if (!IsDebouncing) {
        IsDebouncing = true;
        AsyncUtils.DelayCall (DebouncePeriodMsec, () => {
            IsDebouncing = false;
        });

        _execute ();
    }
}

похоже, что контроль над созданием обоих этих объектов и их взаимозависимостью должен контролироваться извне, а не между самими классами.

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

однако, если вам действительно нужно отложить выполнение, вот что вы можете сделать:

BackgroundWorker barInvoker = new BackgroundWorker();
barInvoker.DoWork += delegate
    {
        Thread.Sleep(TimeSpan.FromSeconds(1));
        bar();
    };
barInvoker.RunWorkerAsync();

это, однако, вызовет bar() в отдельном потоке. Если вам нужно позвонить bar() в исходном потоке вам может понадобиться, чтобы переместить bar() вызов RunWorkerCompleted обработчик или сделать немного поковырявшись с SynchronizationContext.

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

    public void foo() {
        // Do stuff!

        object syncLock = new object();
        lock (syncLock) {
            // Delayed call to bar() after x number of ms
            ThreadPool.QueueUserWorkItem(delegate {
                lock(syncLock) {
                    bar();
                }
            });

            // Do more Stuff
        } 
        // lock now released, bar can begin            
    }
public static class DelayedDelegate
{

    static Timer runDelegates;
    static Dictionary<MethodInvoker, DateTime> delayedDelegates = new Dictionary<MethodInvoker, DateTime>();

    static DelayedDelegate()
    {

        runDelegates = new Timer();
        runDelegates.Interval = 250;
        runDelegates.Tick += RunDelegates;
        runDelegates.Enabled = true;

    }

    public static void Add(MethodInvoker method, int delay)
    {

        delayedDelegates.Add(method, DateTime.Now + TimeSpan.FromSeconds(delay));

    }

    static void RunDelegates(object sender, EventArgs e)
    {

        List<MethodInvoker> removeDelegates = new List<MethodInvoker>();

        foreach (MethodInvoker method in delayedDelegates.Keys)
        {

            if (DateTime.Now >= delayedDelegates[method])
            {
                method();
                removeDelegates.Add(method);
            }

        }

        foreach (MethodInvoker method in removeDelegates)
        {

            delayedDelegates.Remove(method);

        }


    }

}

использование:

DelayedDelegate.Add(MyMethod,5);

void MyMethod()
{
     MessageBox.Show("5 Seconds Later!");
}

Я хотя идеальным решением было бы иметь таймер обработки замедленного действия. FxCop не любит, когда у вас есть интервал менее одной секунды. Мне нужно отложить мои действия до тех пор, пока мой DataGrid не завершит сортировку по столбцу. Я решил, что одноразовый таймер (AutoReset = false) будет решением, и он отлично работает. И, FxCop не позволит мне подавить предупреждение!

нет стандартного способа задержки вызова функции, кроме использования таймера и событий.

Это звучит как GUI anti pattern задержки вызова метода, так что вы можете быть уверены, что форма закончила выкладку. Не очень хорошая идея.

основываясь на ответе Дэвида О'Донохью вот оптимизированная версия отложенного делегата:

using System.Windows.Forms;
using System.Collections.Generic;
using System;

namespace MyTool
{
    public class DelayedDelegate
    {
       static private DelayedDelegate _instance = null;

        private Timer _runDelegates = null;

        private Dictionary<MethodInvoker, DateTime> _delayedDelegates = new Dictionary<MethodInvoker, DateTime>();

        public DelayedDelegate()
        {
        }

        static private DelayedDelegate Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new DelayedDelegate();
                }

                return _instance;
            }
        }

        public static void Add(MethodInvoker pMethod, int pDelay)
        {
            Instance.AddNewDelegate(pMethod, pDelay * 1000);
        }

        public static void AddMilliseconds(MethodInvoker pMethod, int pDelay)
        {
            Instance.AddNewDelegate(pMethod, pDelay);
        }

        private void AddNewDelegate(MethodInvoker pMethod, int pDelay)
        {
            if (_runDelegates == null)
            {
                _runDelegates = new Timer();
                _runDelegates.Tick += RunDelegates;
            }
            else
            {
                _runDelegates.Stop();
            }

            _delayedDelegates.Add(pMethod, DateTime.Now + TimeSpan.FromMilliseconds(pDelay));

            StartTimer();
        }

        private void StartTimer()
        {
            if (_delayedDelegates.Count > 0)
            {
                int delay = FindSoonestDelay();
                if (delay == 0)
                {
                    RunDelegates();
                }
                else
                {
                    _runDelegates.Interval = delay;
                    _runDelegates.Start();
                }
            }
        }

        private int FindSoonestDelay()
        {
            int soonest = int.MaxValue;
            TimeSpan remaining;

            foreach (MethodInvoker invoker in _delayedDelegates.Keys)
            {
                remaining = _delayedDelegates[invoker] - DateTime.Now;
                soonest = Math.Max(0, Math.Min(soonest, (int)remaining.TotalMilliseconds));
            }

            return soonest;
        }

        private void RunDelegates(object pSender = null, EventArgs pE = null)
        {
            try
            {
                _runDelegates.Stop();

                List<MethodInvoker> removeDelegates = new List<MethodInvoker>();

                foreach (MethodInvoker method in _delayedDelegates.Keys)
                {
                    if (DateTime.Now >= _delayedDelegates[method])
                    {
                        method();

                        removeDelegates.Add(method);
                    }
                }

                foreach (MethodInvoker method in removeDelegates)
                {
                    _delayedDelegates.Remove(method);
                }
            }
            catch (Exception ex)
            {
            }
            finally
            {
                StartTimer();
            }
        }
    }
}

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

private static volatile List<System.Threading.Timer> _timers = new List<System.Threading.Timer>();
        private static object lockobj = new object();
        public static void SetTimeout(Action action, int delayInMilliseconds)
        {
            System.Threading.Timer timer = null;
            var cb = new System.Threading.TimerCallback((state) =>
            {
                lock (lockobj)
                    _timers.Remove(timer);
                timer.Dispose();
                action()
            });
            lock (lockobj)
                _timers.Add(timer = new System.Threading.Timer(cb, null, delayInMilliseconds, System.Threading.Timeout.Infinite));
}

это будет работать либо на старых версиях .NET
минусы: будет выполняться в своем собственном потоке

class CancelableDelay
    {
        Thread delayTh;
        Action action;
        int ms;

        public static CancelableDelay StartAfter(int milliseconds, Action action)
        {
            CancelableDelay result = new CancelableDelay() { ms = milliseconds };
            result.action = action;
            result.delayTh = new Thread(result.Delay);
            result.delayTh.Start();
            return result;
        }

        private CancelableDelay() { }

        void Delay()
        {
            try
            {
                Thread.Sleep(ms);
                action.Invoke();
            }
            catch (ThreadAbortException)
            { }
        }

        public void Cancel() => delayTh.Abort();

    }

использование:

var job = CancelableDelay.StartAfter(1000, () => { WorkAfter1sec(); });  
job.Cancel(); //to cancel the delayed job

Comments

    Ничего не найдено.