Apakah ada metode sederhana yang bagus untuk menunda panggilan fungsi sambil membiarkan utas terus berjalan?
misalnya
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
}
Saya sadar bahwa ini dapat dicapai dengan menggunakan timer dan event handler, tapi saya bertanya-tanya apakah ada cara c # standar untuk mencapai ini?
Jika ada yang penasaran, alasan mengapa hal ini diperlukan adalah karena foo () dan bar () berada dalam kelas (tunggal) yang berbeda yang saya perlukan untuk memanggil satu sama lain dalam keadaan luar biasa. Masalahnya adalah hal ini dilakukan saat inisialisasi sehingga foo perlu memanggil bar yang membutuhkan turunan dari kelas foo yang sedang dibuat ... oleh karena itu panggilan tertunda ke bar () untuk memastikan bahwa foo sepenuhnya dijalankan .. Membaca ini kembali hampir mirip dengan desain yang buruk!
EDIT
Saya akan mengambil poin tentang desain yang buruk di bawah saran! Saya sudah lama berpikir bahwa saya mungkin dapat meningkatkan sistem, namun, situasi buruk ini hanya terjadi ketika pengecualian dilemparkan, di lain waktu kedua lajang hidup berdampingan dengan sangat baik. Saya pikir saya tidak akan mengacaukan async-patters yang buruk, melainkan saya akan melakukan refactor inisialisasi salah satu kelas.
Awake
danStart
fase. PadaAwake
fase ini, Anda mengkonfigurasi diri Anda sendiri, dan pada akhir fase ini semua objek diinisialisasi. SelamaStart
fase objek dapat mulai berkomunikasi satu sama lain.Jawaban:
Berkat C # 5/6 modern :)
public void foo() { Task.Delay(1000).ContinueWith(t=> bar()); } public void bar() { // do stuff }
sumber
Saya sendiri telah mencari sesuatu seperti ini - saya menemukan yang berikut, meskipun menggunakan pengatur waktu, ia hanya menggunakannya sekali untuk penundaan awal, dan tidak memerlukan
Sleep
panggilan apa pun ...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 }
(terima kasih kepada Fred Deschenes untuk gagasan membuang pengatur waktu dalam panggilan balik)
sumber
timer
variabel lokal dari dalam lambda yang terikat ke objek delegasicb
menyebabkannya diangkat ke penyimpanan langsung (detail implementasi penutupan) yang akan menyebabkanTimer
objek dapat dijangkau dari perspektif GC selamaTimerCallback
delegasi itu sendiri dapat dijangkau . Dengan kata lain,Timer
objek dijamin tidak akan dikumpulkan sampah sampai setelah objek delegasi dipanggil oleh kumpulan thread.Selain setuju dengan pengamatan desain dari komentator sebelumnya, tidak ada solusi yang cukup bersih untuk saya. .Net 4 menyediakan
Dispatcher
danTask
kelas - kelas yang membuat penundaan eksekusi pada thread saat ini cukup sederhana: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 (); } }
Untuk konteksnya, saya menggunakan ini untuk
ICommand
melepaskan tombol yang diikat ke kiri mouse pada elemen UI. Pengguna mengklik dua kali yang menyebabkan semua jenis malapetaka. (Saya tahu saya juga bisa menggunakanClick
/DoubleClick
penangan, tapi saya ingin solusi yang bekerja denganICommand
s secara keseluruhan).public void Execute(object parameter) { if (!IsDebouncing) { IsDebouncing = true; AsyncUtils.DelayCall (DebouncePeriodMsec, () => { IsDebouncing = false; }); _execute (); } }
sumber
Kedengarannya seperti kontrol pembuatan kedua objek ini dan kesalingtergantungannya perlu dikontrol secara eksternal, bukan antar kelas itu sendiri.
sumber
Ini memang desain yang sangat buruk, apalagi singleton dengan sendirinya adalah desain yang buruk.
Namun, jika Anda benar-benar perlu menunda eksekusi, inilah yang dapat Anda lakukan:
BackgroundWorker barInvoker = new BackgroundWorker(); barInvoker.DoWork += delegate { Thread.Sleep(TimeSpan.FromSeconds(1)); bar(); }; barInvoker.RunWorkerAsync();
Namun, ini akan dijalankan
bar()
pada utas terpisah. Jika Anda perlu memanggilbar()
di utas asli, Anda mungkin perlu memindahkanbar()
pemanggilan keRunWorkerCompleted
penangan atau melakukan sedikit peretasanSynchronizationContext
.sumber
Yah, saya harus setuju dengan poin "desain" ... tetapi Anda mungkin dapat menggunakan Monitor untuk memberi tahu yang satu ketika yang lain melewati bagian kritis ...
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 }
sumber
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); } } }
Pemakaian:
DelayedDelegate.Add(MyMethod,5); void MyMethod() { MessageBox.Show("5 Seconds Later!"); }
sumber
Saya pikir solusi yang tepat adalah memiliki pengatur waktu menangani tindakan yang tertunda. FxCop tidak suka jika Anda memiliki interval kurang dari satu detik. Saya perlu menunda tindakan saya sampai SETELAH DataGrid saya selesai mengurutkan menurut kolom. Saya pikir timer sekali pakai (AutoReset = false) akan menjadi solusinya, dan berfungsi dengan sempurna. DAN, FxCop tidak akan membiarkan saya menekan peringatan tersebut!
sumber
Ini akan berfungsi baik pada versi .NET
Kontra: akan dijalankan di utasnya sendiri
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(); }
Pemakaian:
var job = CancelableDelay.StartAfter(1000, () => { WorkAfter1sec(); }); job.Cancel(); //to cancel the delayed job
sumber
Tidak ada cara standar untuk menunda panggilan ke suatu fungsi selain menggunakan timer dan acara.
Ini terdengar seperti pola anti GUI untuk menunda panggilan ke suatu metode sehingga Anda bisa yakin formulir telah selesai disusun. Bukan ide yang bagus.
sumber
Berdasarkan jawaban dari David O'Donoghue, berikut adalah versi Delayed Delegate yang dioptimalkan:
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(); } } } }
Kelas bisa sedikit lebih ditingkatkan dengan menggunakan kunci unik untuk para delegasi. Karena jika Anda menambahkan delegasi yang sama untuk kedua kalinya sebelum yang pertama diaktifkan, Anda mungkin mendapatkan masalah dengan kamus.
sumber
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)); }
sumber