Задержанные вызовы функций - PullRequest
       23

Задержанные вызовы функций

67 голосов
/ 13 февраля 2009

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

, например

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

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

Ответы [ 12 ]

122 голосов
/ 25 декабря 2015

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

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

public void bar()
{
    // do stuff
}
92 голосов
/ 11 июля 2011

Я сам искал что-то подобное - я придумал следующее, хотя он использует таймер, он использует его только один раз для начальной задержки и не требует никаких вызовов 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
}

(спасибо Фреду Дешену за идею размещения таймера в обратном вызове)

15 голосов
/ 05 марта 2014

Кроме того, что я согласился с проектными замечаниями предыдущих комментаторов, ни одно из решений не было достаточно чистым для меня. .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, но я хотел решение, которое работает с ICommand s по всем направлениям).

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

        _execute ();
    }
}
7 голосов
/ 13 февраля 2009

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

5 голосов
/ 13 февраля 2009

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

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

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

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

2 голосов
/ 10 января 2012
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!");
}
2 голосов
/ 13 февраля 2009

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

    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            
    }
1 голос
/ 20 апреля 2018

Это будет работать на более старых версиях .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
1 голос
/ 26 января 2012

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

0 голосов
/ 31 января 2014
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));
}
...