Geciken işlev çağrıları


92

İş parçacığının yürütülmeye devam etmesine izin verirken bir işlev çağrısını geciktirmenin güzel ve basit bir yöntemi var mı?

Örneğin

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
}

Bunun bir zamanlayıcı ve olay işleyicileri kullanılarak elde edilebileceğinin farkındayım, ancak bunu başarmanın standart bir c # yolu olup olmadığını merak ediyordum.

Meraklı biri varsa, bunun gerekli olmasının nedeni foo () ve bar () 'ın istisnai durumlarda birbirlerini çağırmam gereken farklı (tekli) sınıflarda olmasıdır. Sorun şu ki, bunun başlatmada yapılması, bu yüzden foo'nun, oluşturulan foo sınıfının bir örneğine ihtiyaç duyan bar'ı çağırması gerekiyor ... bu nedenle, foo'nun tam olarak yerleşik olmasını sağlamak için bar () 'a gecikmeli çağrı. neredeyse kötü tasarım kokusu!

DÜZENLE

Kötü tasarımla ilgili noktaları tavsiye altında alacağım! Uzun zamandır sistemi geliştirebileceğimi düşündüm, ancak bu kötü durum sadece bir istisna atıldığında ortaya çıkıyor, diğer tüm zamanlarda iki singleton çok güzel bir şekilde bir arada var oluyor. Kötü eşzamansız kalıplarla uğraşmayacağımı düşünüyorum, bunun yerine sınıflardan birinin ilklendirmesini yeniden düzenleyeceğim.


Bunu düzeltmeniz gerekiyor, ancak konuları kullanarak (veya bu konuda başka bir eşzamansız uygulama) değil
ShuggyCoUk

1
Nesne başlatmayı senkronize etmek için evreleri kullanmak zorunda olmak, başka bir yol seçmeniz gerektiğinin işaretidir. Orkestratör daha iyi bir seçenek gibi görünüyor.
thinkbeforecoding

1
Diriliş! - Tasarım hakkında yorum yaparak, iki aşamalı bir başlatma tercihi yapabilirsiniz. Unity3D API'den çizim, vardır Awakeve Startaşamalar. In Awakefaz, kendini yapılandırmak ve bu aşamanın sonuna kadar tüm nesneler başlatılır. StartAşama sırasında nesneler birbirleriyle iletişim kurmaya başlayabilir.
cod3monk3y

1
Kabul edilen cevabın değiştirilmesi gerekiyor
Brian Webster

Yanıtlar:


178

Modern C # 5/6 sayesinde :)

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

public void bar()
{
    // do stuff
}

15
Bu cevap, 2 nedenden dolayı harika. Kod basitliği ve Delay'in bir iş parçacığı oluşturmaması veya diğer Task.Run veya Task.StartNew gibi iş parçacığı havuzunu kullanmaması ... dahili olarak bir zamanlayıcıdır.
Zyo

İyi bir çözüm.
x4h1d

6
Ayrıca biraz daha temiz (IMO) eşdeğer bir sürüme dikkat edin: Task.Delay (TimeSpan.FromSeconds (1)). ContinueWith (_ => bar ());
Taran

5
@Zyo Aslında farklı bir ileti dizisi kullanıyor. Bir UI öğesine ondan erişmeyi deneyin ve bir istisna tetikleyecektir.
TudorT

@TudorT - Zyo, timer olaylarını çalıştıran halihazırda var olan bir iş parçacığı üzerinde çalıştığı konusunda doğruysa, onun amacı, yeni bir iş parçacığı oluşturmak için fazladan kaynakları tüketmemesi veya iş parçacığı havuzunda kuyruğa girmemesidir . (Bir zamanlayıcı oluşturmanın, görevi iş parçacığı havuzuna sıralamaktan önemli ölçüde daha ucuz olup olmadığını bilmeme rağmen - ki bu AYRICA bir iş parçacığı oluşturmaz, bu iş parçacığı havuzunun tüm noktasıdır.)
ToolmakerSteve

96

Ben de böyle bir şey arıyordum - aşağıdakini buldum, bir zamanlayıcı kullanmasına rağmen, ilk gecikme için yalnızca bir kez kullanıyor ve herhangi bir Sleeparama gerektirmiyor ...

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
}

(Geri aramada zamanlayıcıyı elden çıkarma fikri için Fred Deschenes'e teşekkürler )


3
Genel olarak bir işlev çağrısını geciktirmek için en iyi yanıtın bu olduğunu düşünüyorum. İş parçacığı yok, arka plan çalışması yok, uyku yok. Zamanlayıcı çok verimlidir ve bellek / işlemci açısından önemlidir.
Zyo

1
@Zyo, yorumunuz için teşekkürler - evet zamanlayıcılar etkilidir ve bu tür bir gecikme birçok durumda, özellikle de bildirim olayları için herhangi bir desteği olmayan kontrolünüz dışındaki bir şeyle arabirim oluştururken kullanışlıdır.
dodgy_coder

Zamanlayıcıyı Ne Zaman Atarsınız?
Didier A.

1
Burada eski bir iş parçacığı canlandırılıyor, ancak zamanlayıcı şu şekilde düzenlenebilir: public static void CallWithDelay (Eylem yöntemi, int gecikme) {Zamanlayıcı zamanlayıcı = null; var cb = new TimerCallback ((durum) => {method (); timer.Dispose ();}); timer = new Timer (cb, null, delay, Timeout.Infinite); } DÜZENLEME: Görünüşe göre yorumlarda kod gönderemiyoruz ... VisualStudio bunu kopyalayıp yapıştırdığınızda düzgün şekilde biçimlendirmelidir: P
Fred Deschenes

6
@dodgy_coder Yanlış. timerTemsilci nesnesine bağlanan lambda içinden yerel değişkeni kullanmak, temsilcinin kendisi erişilebilir olduğu sürece nesnenin GC'nin bakış açısından erişilebilir cbolmasına neden olacak bir anon deposuna (kapanış uygulama ayrıntısı) kaldırılmasına neden olur. . Başka bir deyişle, nesne olduğu garanti temsilci nesne iplik havuz çağrılır sonrasına kadar toplanan çöp olmamaya. TimerTimerCallbackTimer
cdhowie

15

Önceki yorumcuların tasarım gözlemlerine katılmanın yanı sıra, çözümlerin hiçbiri benim için yeterince temiz değildi. .Net 4 sağlar Dispatcherve Taskyürütme geciktirmek yapmak sınıflar geçerli iş parçacığı üzerinde oldukça basit:

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

Bağlam için, bunu ICommandbir UI öğesinde sol fare düğmesine bağlı bir düğmeyi iptal etmek için kullanıyorum . Kullanıcılar çift tıklıyor ve bu da her türlü hasara neden oluyordu. (Ayrıca Click/ DoubleClickişleyicileri de kullanabileceğimi biliyorum , ancak ICommandher yerde s ile çalışan bir çözüm istedim ).

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

        _execute ();
    }
}

7

Hem bu nesnelerin yaratılmasının kontrolü hem de karşılıklı bağımlılıklarının sınıflar arasında değil, dışarıdan kontrol edilmesi gerektiği gibi görünüyor.


+1, bu bir tür orkestratör ve belki de bir fabrikaya ihtiyacınız
varmış gibi geliyor

5

Gerçekten de çok kötü bir tasarım, bırakın tek başına kötü tasarım.

Ancak, yürütmeyi gerçekten ertelemeniz gerekiyorsa, şunları yapabilirsiniz:

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

Ancak bu, bar()ayrı bir iş parçacığı üzerinde başlatılacaktır. bar()Orijinal iş parçacığını aramanız gerekirse, bar()çağrıyı RunWorkerCompletedişleyiciye taşımanız veya biraz hacklemeniz gerekebilir SynchronizationContext.


3

Pekala, "tasarım" noktasına katılıyorum ... ama biri kritik bölümü geçtiğinde diğerini haberdar etmek için bir Monitör kullanabilirsiniz ...

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

        }


    }

}

Kullanım:

DelayedDelegate.Add(MyMethod,5);

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

1
Zamanlayıcının her 250 milisaniyede bir çalışmasını önlemek için biraz mantık koymanızı tavsiye ederim. Birincisi: İzin verilen minimum aralığınız 1 saniye olduğu için gecikmeyi 500 milisaniyeye çıkarabilirsiniz. İkinci olarak: Zamanlayıcıyı yalnızca yeni temsilciler eklendiğinde başlatabilir ve başka temsilci kalmadığında durdurabilirsiniz. Yapacak bir şey olmadığında CPU döngülerini kullanmaya devam etmek için bir neden yok. Üçüncüsü: zamanlayıcı aralığını tüm delegeler için minimum gecikmeye ayarlayabilirsiniz. Dolayısıyla, yapacak bir şey olup olmadığını görmek için her 250 milisaniyede bir uyanmak yerine, yalnızca bir temsilciyi çağırması gerektiğinde uyanır.
Pic Mickael

MethodInvoker bir Windows.Forms nesnesidir. Web geliştiricileri için bir alternatif var mı lütfen? yani: System.Web.UI.WebControls ile çakışmayan bir şey.
Fandango68

1

Bence mükemmel çözüm, gecikmeli eylemi bir zamanlayıcıya sahip olmaktır. FxCop, bir saniyeden daha kısa bir aralığınızın olmasından hoşlanmaz. DataGrid'im sütuna göre sıralamayı tamamladıktan SONRA eylemlerimi ertelemem gerekiyor. Tek seferlik bir zamanlayıcının (AutoReset = false) çözüm olacağını düşündüm ve mükemmel çalışıyor. VE, FxCop uyarıyı bastırmama izin vermiyor!


1

Bu, ya eski .NET
Eksileri sürümlerinde de çalışır : kendi iş parçacığında yürütülecektir

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

    }

Kullanım:

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

0

Zamanlayıcı ve olaylar kullanmak dışında bir işleve gelen çağrıyı geciktirmenin standart bir yolu yoktur.

Bu, bir yönteme yapılan bir çağrıyı geciktiren GUI karşıtı modele benziyor, böylece formun düzeninin tamamlandığından emin olabilirsiniz. İyi bir fikir değil.


0

David O'Donoghue'un cevabına dayanarak, Gecikmeli Delege'nin optimize edilmiş bir versiyonu:

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

Temsilciler için benzersiz bir anahtar kullanılarak sınıf biraz daha iyileştirilebilir. Çünkü aynı delegeyi, ilk atanmadan önce ikinci kez eklerseniz, sözlükle ilgili bir sorunla karşılaşabilirsiniz.


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));
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.