지연된 함수 호출


91

스레드가 계속 실행되도록하는 동안 함수 호출을 지연시키는 멋진 간단한 방법이 있습니까?

예 :

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는 생성되는 foo 클래스의 인스턴스가 필요한 bar를 호출해야합니다. 따라서 foo가 완전히 인스턴스화되었는지 확인하기 위해 bar ()에 대한 지연된 호출이 필요합니다. 거의 디자인이 안좋아!

편집하다

나쁜 디자인에 대한 요점을 조언하에 드리겠습니다! 나는 시스템을 개선 할 수있을 것이라고 오랫동안 생각했지만,이 끔찍한 상황 은 예외가 발생했을 때만 발생하고 다른 모든 경우에는 두 개의 싱글 톤이 매우 잘 공존합니다. 나는 불쾌한 비동기 패턴을 엉망으로 만들지 않고 클래스 중 하나의 초기화를 리팩터링 할 것이라고 생각합니다.


당신은 그것을 고칠 필요가 있지만 쓰레드 (또는 그 문제에 대한 다른 asyn 관행)를 사용하지 않고
ShuggyCoUk

1
객체 초기화를 동기화하기 위해 스레드를 사용해야하는 것은 다른 방법을 취해야한다는 신호입니다. 오케 스트레이터가 더 나은 옵션 인 것 같습니다.
thinkbeforecoding

1
부활! -디자인에 대해 언급하면서 2 단계 초기화를 선택할 수 있습니다. Unity3D API에서 그리기에는 AwakeStart단계가 있습니다. 이 Awake단계에서는 직접 구성하고이 단계가 끝나면 모든 개체가 초기화됩니다. Start단계 동안 개체는 서로 통신을 시작할 수 있습니다.
cod3monk3y

1
허용 대답의 요구가 변경되는
브라이언 웹스터

답변:


177

최신 C # 5/6 덕분에 :)

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

public void bar()
{
    // do stuff
}

15
이 답변은 두 가지 이유로 굉장합니다. 코드 단순성과 Delay는 스레드를 생성하지 않으며 다른 Task.Run 또는 Task.StartNew와 같이 스레드 풀을 사용하지 않는다는 사실은 내부적으로 타이머입니다.
Zyo

괜찮은 솔루션.
x4h1d

5
또한 약간 더 깔끔한 (IMO) 해당 버전에 유의하십시오. Task.Delay (TimeSpan.FromSeconds (1)). ContinueWith (_ => bar ());
Taran

5
@Zyo 실제로는 다른 스레드를 사용합니다. 여기에서 UI 요소에 액세스하면 예외가 트리거됩니다.
TudorT 2018 년

@TudorT-Zyo가 타이머 이벤트를 실행하는 이미 존재하는 스레드에서 실행되는 것이 맞다면, 그의 요점은 새 스레드를 생성 하거나 스레드 풀에 대기 하는 추가 리소스 소비하지 않는다는 것 입니다. (타이머를 만드는 것이 스레드 풀에 작업을 대기열에 넣는 것보다 훨씬 저렴한지는 모르겠지만 스레드 풀의 전체 지점 인 스레드를 만들지 않습니다.)
ToolmakerSteve

96

나는 이것과 같은 것을 찾고 있었다-나는 타이머를 사용하지만 초기 지연을 위해 한 번만 사용하고 어떤 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
나는 이것이 일반적으로 함수 호출을 지연시키는 가장 좋은 대답이라고 생각합니다. 스레드, 백그라운드 작업, 수면 없음. 타이머는 매우 효율적이고 메모리 / cpu 현명합니다.
Zyo

1
@Zyo, 귀하의 의견에 감사드립니다-예 타이머는 효율적이며 이러한 종류의 지연은 알림 이벤트를 지원하지 않는 많은 상황에서 특히 제어 할 수없는 것에 인터페이스 할 때 유용합니다.
dodgy_coder

타이머는 언제 폐기합니까?
Didier A.

1
여기에서 오래된 스레드를 되살 리지만 타이머는 다음과 같이 처리 될 수 있습니다. public static void CallWithDelay (Action method, int delay) {Timer timer = null; var cb = new TimerCallback ((state) => {method (); timer.Dispose ();}); timer = new Timer (cb, null, delay, Timeout.Infinite); } 편집 : 우리가 주석에 코드를 게시 할 수없는 것 같습니다. 어쨌든 복사 / 붙여 넣기 할 때 VisualStudio가 올바르게 형식을 지정해야합니다. P
Fred Deschenes

6
@dodgy_coder 틀 렸습니다. timer델리게이트 객체에 바인딩 된 람다 내 에서 로컬 변수를 사용 cb하면 델리게이트 자체에 도달 할 수 Timer있는 한 GC의 관점에서 객체에 도달 할 수 있도록 하는 anon 저장소 (클로저 구현 세부 정보)에 게양 됩니다. TimerCallback. 즉, Timer객체가되어 보장 대리자 개체가 스레드 풀에 의해 호출 될 때까지 가비지 수집 할 수 없습니다.
cdhowie

15

이전 댓글 작성자의 설계 관찰에 동의하는 것 외에는 나에게 충분한 솔루션이 없었습니다. 닷넷 4 제공 DispatcherTask실행을 지연 만든다 클래스 현재 스레드에 아주 간단합니다 :

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

맥락을 위해, 나는 이것을 사용하여 ICommandUI 요소에서 왼쪽 마우스 버튼에 묶인 것을 디 바운스합니다 . 사용자는 모든 종류의 혼란을 일으키는 더블 클릭을합니다. (나는 Click/ DoubleClick핸들러를 사용할 수도 있다는 것을 알고 있지만 ICommand전반적 으로 s 와 함께 작동하는 솔루션을 원했습니다 ).

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

        _execute ();
    }
}

7

이러한 객체의 생성 제어와 상호 의존성은 클래스 자체가 아닌 외부에서 제어해야하는 것처럼 들립니다.


+1, 이것은 일종의 오케 스트레이터와 아마도 공장이 필요한 것처럼 들립니다
ng5000

5

싱글 톤 자체가 나쁜 디자인은 말할 것도없고 실제로 매우 나쁜 디자인입니다.

그러나 실제로 실행을 지연해야하는 경우 수행 할 수있는 작업은 다음과 같습니다.

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

그러나 이것은 bar()별도의 스레드 에서 호출 됩니다. bar()원래 스레드에서 호출해야하는 경우 bar()호출을 RunWorkerCompleted핸들러 로 이동 하거나 SynchronizationContext.


3

글쎄, 나는 "디자인"요점에 동의해야 할 것이다. 그러나 당신은 아마 모니터를 사용하여 다른 하나가 중요한 섹션을 지나면 알릴 수있을 것이다.

    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            
    }

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

1
250 밀리 초마다 타이머가 실행되지 않도록 논리를 추가하는 것이 좋습니다. 첫째 : 허용되는 최소 간격이 1 초이므로 지연을 500 밀리 초로 늘릴 수 있습니다. 둘째 : 새 대리인이 추가 된 경우에만 타이머를 시작하고 더 이상 대리인이 없으면 중지 할 수 있습니다. 할 일이 없을 때 CPU주기를 계속 사용할 이유가 없습니다. 셋째 : 타이머 간격을 모든 델리게이트의 최소 지연으로 설정할 수 있습니다. 따라서 할 일이 있는지 확인하기 위해 250 밀리 초마다 깨어나는 대신 델리게이트를 호출해야 할 때만 깨어납니다.
Pic Mickael 2013 년

MethodInvoker는 Windows.Forms 개체입니다. 웹 개발자를위한 대안이 있습니까? 즉 : System.Web.UI.WebControls와 충돌하지 않는 것.
Fandango68

1

완벽한 해결책은 타이머가 지연된 작업을 처리하도록하는 것입니다. FxCop은 간격이 1 초 미만인 경우를 좋아하지 않습니다. DataGrid가 열별로 정렬을 완료 한 후 작업을 지연해야합니다. 원샷 타이머 (AutoReset = false)가 해결책이 될 것이라고 생각했으며 완벽하게 작동합니다. 그리고 FxCop은 경고를 억제하지 못하게합니다!


1

이것은 이전 버전의 .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

0

타이머 및 이벤트를 사용하는 것 외에 함수 호출을 지연하는 표준 방법은 없습니다.

이것은 폼의 레이아웃이 완료되었는지 확인할 수 있도록 메서드 호출을 지연시키는 GUI 안티 패턴처럼 들립니다. 좋은 생각이 아닙니다.


0

David O'Donoghue의 답변을 바탕으로 최적화 된 Delayed Delegate 버전이 있습니다.

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

대리자에 대한 고유 키를 사용하여 클래스를 약간 더 향상시킬 수 있습니다. 첫 번째 대리자가 실행되기 전에 동일한 대리자를 두 번 추가하면 사전에 문제가 발생할 수 있기 때문입니다.


0
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));
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.