विलंबित फ़ंक्शन कॉल


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
}

मुझे पता है कि यह एक टाइमर और इवेंट हैंडलर का उपयोग करके प्राप्त किया जा सकता है, लेकिन मैं सोच रहा था कि क्या इसे प्राप्त करने के लिए एक मानक सी # तरीका है?

यदि कोई भी जिज्ञासु है, तो इसका कारण यह है कि फू () और बार () विभिन्न (सिंगलटन) कक्षाओं में होते हैं, जिन्हें असाधारण परिस्थितियों में एक दूसरे को कॉल करने की आवश्यकता होती है। समस्या यह है कि यह आरंभीकरण पर किया जाता है इसलिए फू को बार को कॉल करने की आवश्यकता होती है जिसे फू क्लास के एक उदाहरण की आवश्यकता होती है जो बनाया जा रहा है ... इसलिए बार को विलंबित कॉल () यह सुनिश्चित करने के लिए कि फू पूरी तरह से अस्थिर है .. इसे वापस पढ़ना खराब डिजाइन के लगभग स्मैक!

संपादित करें

मैं सलाह के तहत खराब डिजाइन के बारे में अंक लूंगा! मैंने लंबे समय से सोचा है कि मैं सिस्टम को बेहतर बनाने में सक्षम हो सकता हूं, हालांकि, यह बुरा स्थिति केवल तब होती है जब एक अपवाद फेंक दिया जाता है, अन्य सभी समय पर दो एकल एकल बहुत अच्छी तरह से मौजूद होते हैं। मुझे लगता है कि मैं गंदा async-patters के साथ खिलवाड़ नहीं करने जा रहा हूं, बल्कि मैं कक्षाओं में से एक के प्रारंभिककरण को रिफलेक्टर करने जा रहा हूं।


आपको इसे ठीक करने की आवश्यकता है, लेकिन थ्रेड्स (या उस मामले के लिए किसी अन्य
एसिन प्रैक्टिस

1
ऑब्जेक्ट इनिशियलाइज़ेशन को सिंक्रोनाइज़ करने के लिए थ्रेड्स का उपयोग करना इस बात का संकेत है कि आपको दूसरा तरीका लेना चाहिए। ऑर्केस्ट्रेटर एक बेहतर विकल्प लगता है।
Thinkbeforecoding

1
जी उठने! - डिजाइन पर टिप्पणी करते हुए, आप एक दो-चरण के आरंभीकरण के लिए एक विकल्प बना सकते हैं। यूनिटी 3 डी एपीआई से ड्राइंग, चरण Awakeऔर Startचरण हैं। में Awakeचरण, आप अपने आप को कॉन्फ़िगर करें, और इस चरण के अंत तक सभी वस्तुओं प्रारंभ कर रहे हैं। Startचरण के दौरान ऑब्जेक्ट एक दूसरे के साथ संचार शुरू कर सकते हैं।
cod3monk3y

1
स्वीकृत उत्तर को बदलने की आवश्यकता है
ब्रायन वेबस्टर

जवाबों:


177

धन्यवाद आधुनिक C # 5/6 :)

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

public void bar()
{
    // do stuff
}

15
यह उत्तर 2 कारणों से बहुत बढ़िया है। कोड सादगी और तथ्य यह है कि देरी एक धागा नहीं बनाते हैं और न ही अन्य Task.Run या Task.StartNew की तरह थ्रेड पूल का उपयोग करते हैं ... यह आंतरिक रूप से एक टाइमर है।
Zyo

एक सभ्य समाधान।
x4h1d

5
थोड़ा क्लीनर (IMO) के बराबर संस्करण पर भी ध्यान दें: Task.Delay (TimeSpan.FromSeconds (1))। ContinueWith (_ => बार ());
तरन

5
@Zyo वास्तव में यह एक अलग धागे का उपयोग करता है। इसमें से एक UI तत्व तक पहुँचने का प्रयास करें और यह एक अपवाद को ट्रिगर करेगा।
ट्यूडोर टी

@ ट्यूडरटी - अगर Zyo सही है कि यह पहले से मौजूद थ्रेड पर चलता है जो टाइमर इवेंट चलाता है, तो उसका कहना यह है कि यह नए थ्रेड बनाने के लिए अतिरिक्त संसाधनों का उपभोग नहीं करता है , और न ही थ्रेड पूल को कतारबद्ध करता है। (हालांकि मुझे नहीं पता कि एक टाइमर बनाने से थ्रेड पूल के लिए एक कार्य को कतारबद्ध करने की तुलना में काफी सस्ता है - जो कि ALSO एक धागा नहीं बनाता है, जो कि थ्रेड पूल का पूरा बिंदु है।)
टूलमेकरसैट

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
}

( कॉलबैक के भीतर टाइमर को निपटाने के विचार के लिए फ्रेड डेसचेन का धन्यवाद )


3
मुझे लगता है कि यह सामान्य रूप से एक फ़ंक्शन कॉल में देरी के लिए सबसे अच्छा जवाब है। कोई धागा नहीं, कोई पृष्ठभूमि काम नहीं कर रही, कोई नींद नहीं। टाइमर बहुत कुशल और स्मृति / सीपीयू बुद्धिमान हैं।
ज़ियो

1
@Zyo, आपकी टिप्पणी के लिए धन्यवाद - हाँ टाइमर कुशल हैं, और इस तरह की देरी कई स्थितियों में उपयोगी है, खासकर जब आपके नियंत्रण से बाहर होने वाली किसी चीज के लिए इंटरफेसिंग - जिसमें अधिसूचना घटनाओं के लिए कोई समर्थन नहीं है।
dodgy_coder

जब आप टाइमर का निपटान करते हैं?
डिडियर ए।

1
यहां एक पुराने धागे को पुनर्जीवित करना, लेकिन टाइमर को इस तरह से निपटाया जा सकता है: सार्वजनिक स्थैतिक शून्य CallWithDelay (एक्शन विधि, इंट देरी) {टाइमर टाइमर = अशक्त; var cb = new TimerCallback ((State) => {method (); टाइमर .ispose ()}}); टाइमर = नया टाइमर (सीबी, शून्य, देरी, टाइमआउट। अनफ़िनिट); } संपादित करें: ऐसा लगता है कि हम टिप्पणियों में कोड पोस्ट नहीं कर सकते ... VisualStudio को इसे ठीक से प्रारूपित करना चाहिए जब आप इसे वैसे भी कॉपी / पेस्ट करते हैं: P
फ्रेड डेसचेन

6
@ डोडी_कोडर गलत। timerलैम्बडा के भीतर से स्थानीय वैरिएबल का उपयोग करना जो डेलीगेट ऑब्जेक्ट के लिए बाध्य हो जाता है, cbइसका कारण यह होता है कि इसे ऑनर स्टोर (क्लोजर इम्प्लीमेंटेशन डिटेल) पर फहराया जाता Timerहै, जो जीसी के नजरिए से ऑब्जेक्ट को तब तक पहुंचाने का कारण बनेगा, जब तक TimerCallbackडेलीगेट अपने आप ही पहुंच नहीं जाता। । दूसरे शब्दों में, इस Timerबात की गारंटी दी जाती है कि थ्रेड पूल द्वारा डलवाई गई डेलीगेट के जमा होने के बाद तक कचरा इकट्ठा न किया जाए।
cdhowie

15

पिछले टिप्पणीकारों की डिजाइन टिप्पणियों से सहमत होने के अलावा, कोई भी समाधान मेरे लिए पर्याप्त रूप से साफ नहीं था। .नेट 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 ();
    }
}

संदर्भ के लिए, मैं इसका उपयोग ICommandUI तत्व पर एक बाईं माउस बटन से बंधे का उपयोग करने के लिए कर रहा हूँ । उपयोगकर्ता डबल क्लिक कर रहे हैं जो सभी प्रकार के कहर का कारण बन रहा था। (मुझे पता है कि मैं Click/ DoubleClickहैंडलर का भी उपयोग कर सकता हूं , लेकिन मैं एक समाधान चाहता था ICommandजो बोर्ड भर में काम करता है )।

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 मिलीसेकंड को चलाने के लिए टाइमर से बचने के लिए कुछ तर्क रखने की सलाह दूंगा। पहला: आप देरी को 500 मिलीसेकंड तक बढ़ा सकते हैं क्योंकि आपका न्यूनतम अनुमत अंतराल 1 सेकंड है। दूसरा: आप टाइमर तब ही शुरू कर सकते हैं जब नए डेलीगेट्स जोड़े जाते हैं, और जब कोई और डेलीगेट्स नहीं होते हैं तो इसे रोक सकते हैं। सीपीयू चक्र का उपयोग करने का कोई कारण नहीं है जब कुछ भी नहीं करना है। तीसरा: आप सभी प्रतिनिधियों के न्यूनतम विलंब के लिए टाइमर अंतराल सेट कर सकते हैं। इसलिए यह तभी उठता है जब उसे किसी प्रतिनिधि को आमंत्रित करने की आवश्यकता होती है, बजाय इसके कि हर 250 मिलीसेकंड जागने के बाद यह देखने के लिए कि क्या कुछ करना है।
Pic मिकाएल

MethodInvoker एक Windows.Forms ऑब्जेक्ट है। क्या वेब डेवलपर के लिए कोई विकल्प है? यानी: ऐसा कुछ जो System.Web.UI.ebControls के साथ टकराता नहीं है।
Fandango68

1

हालांकि, सही समाधान के लिए एक टाइमर के साथ विलंबित कार्रवाई को संभालना होगा। FxCop पसंद नहीं है जब आप एक अंतराल कम है तो एक सेकंड। जब तक मेरे डेटाग्रिड ने कॉलम द्वारा छांटना पूरा नहीं किया है, तब तक मुझे अपने कार्यों में देरी करने की आवश्यकता है। मुझे लगा कि एक-शॉट टाइमर (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

टाइमर और घटनाओं का उपयोग करने के अलावा किसी फ़ंक्शन को कॉल करने में देरी करने का कोई मानक तरीका नहीं है।

यह एक विधि को कॉल करने में देरी करने के जीयूआई विरोधी पैटर्न की तरह लगता है ताकि आप सुनिश्चित कर सकें कि फॉर्म समाप्त हो गया है। अच्छा विचार नहीं।


0

डेविड ओ'डोनग्यू के जवाब पर बिल्डिंग यहां डिलेड डेलीगेट का एक अनुकूलित संस्करण है:

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));
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.