Отсроченные вызовы функций

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

например.

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
}

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

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

ИЗМЕНИТЬ

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

Ответ 1

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

Ответ 2

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

(благодаря Fred Deschenes за идею утилизации таймера в обратном вызове)

Ответ 3

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

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

public void bar()
{
    // do stuff
}

Ответ 4

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

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 по всей доске).

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

        _execute ();
    }
}

Ответ 5

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

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

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

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

Ответ 6

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!");
}

Ответ 7

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

    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            
    }

Ответ 8

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

Ответ 9

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

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

Ответ 10

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

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

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

Ответ 11

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