Czy istnieje ładna, prosta metoda opóźnienia wywołania funkcji, pozwalając jednocześnie wątkowi kontynuować wykonywanie?
na przykład
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
}
Zdaję sobie sprawę, że można to osiągnąć za pomocą licznika czasu i programów obsługi zdarzeń, ale zastanawiałem się, czy istnieje standardowy sposób C #, aby to osiągnąć?
Jeśli ktoś jest ciekawy, powodem, dla którego jest to wymagane, jest to, że foo () i bar () są w różnych (singleton) klasach, które muszę nazywać się w wyjątkowych okolicznościach. Problem polega na tym, że odbywa się to podczas inicjalizacji, więc foo musi wywołać bar, który potrzebuje instancji klasy foo, która jest tworzona ... stąd opóźnione wywołanie bar (), aby upewnić się, że foo jest w pełni zainicjowane. Czytanie tego z powrotem prawie trąci złym projektem!
EDYTOWAĆ
Rozważę punkty dotyczące złego projektu! Od dawna myślałem, że może uda mi się ulepszyć system, jednak ta paskudna sytuacja występuje tylko wtedy, gdy zostanie zgłoszony wyjątek, we wszystkich innych przypadkach dwa singletony bardzo ładnie współistnieją. Myślę, że nie zamierzam mieszać z paskudnymi wzorami asynchronicznymi, raczej zamierzam refaktoryzować inicjalizację jednej z klas.
Awake
iStart
fazy. W tejAwake
fazie konfigurujesz siebie, a pod koniec tej fazy wszystkie obiekty są inicjalizowane. W tejStart
fazie obiekty mogą zacząć komunikować się ze sobą.Odpowiedzi:
Dzięki nowoczesnemu C # 5/6 :)
public void foo() { Task.Delay(1000).ContinueWith(t=> bar()); } public void bar() { // do stuff }
źródło
Sam szukałem czegoś takiego - wymyśliłem co następuje, chociaż korzysta z timera, używa go tylko raz do początkowego opóźnienia i nie wymaga żadnych
Sleep
połączeń ...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 }
(podziękowania dla Freda Deschenesa za pomysł pozbycia się timera w wywołaniu zwrotnym)
źródło
timer
zmiennej lokalnej z poziomu lambda, która jest powiązana z obiektem delegata,cb
powoduje, że jest ona przenoszona do magazynu anon (szczegóły implementacji zamknięcia), co spowoduje, żeTimer
obiekt będzie dostępny z perspektywy GC tak długo, jakTimerCallback
sam delegat jest osiągalny . Innymi słowy, gwarantuje się , żeTimer
obiekt nie zostanie wyrzucony do pamięci, dopóki obiekt delegata nie zostanie wywołany przez pulę wątków.Pomijając zgodzenie się z obserwacjami projektowymi poprzednich komentatorów, żadne z rozwiązań nie było dla mnie wystarczająco czyste. .Net 4 zapewnia
Dispatcher
iTask
klasy, które sprawiają, że opóźnianie wykonania bieżącego wątku jest dość proste: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 (); } }
W kontekście używam tego do odbicia
ICommand
powiązanego z lewym przyciskiem myszy w elemencie interfejsu użytkownika. Użytkownicy klikali dwukrotnie, co powodowało różnego rodzaju spustoszenie. (Wiem, że mógłbym również użyćClick
/DoubleClick
handlers, ale chciałem rozwiązania, które działa zICommand
wszystkimi elementami).public void Execute(object parameter) { if (!IsDebouncing) { IsDebouncing = true; AsyncUtils.DelayCall (DebouncePeriodMsec, () => { IsDebouncing = false; }); _execute (); } }
źródło
Wygląda na to, że kontrola tworzenia obu tych obiektów i ich współzależności musi być kontrolowana zewnętrznie, a nie między samymi klasami.
źródło
To rzeczywiście bardzo zły projekt, nie mówiąc już o singletonie sam w sobie jest złym projektem.
Jeśli jednak naprawdę musisz opóźnić wykonanie, oto co możesz zrobić:
BackgroundWorker barInvoker = new BackgroundWorker(); barInvoker.DoWork += delegate { Thread.Sleep(TimeSpan.FromSeconds(1)); bar(); }; barInvoker.RunWorkerAsync();
Spowoduje to jednak wywołanie
bar()
w osobnym wątku. Jeśli chcesz zadzwonićbar()
w oryginalnym wątku, może być konieczne przeniesieniebar()
wywołania do modułuRunWorkerCompleted
obsługi lub trochę włamaniaSynchronizationContext
.źródło
Cóż, musiałbym zgodzić się z punktem „projektowania” ... ale prawdopodobnie możesz użyć monitora, aby powiadomić jednego, gdy drugi znajdzie się poza krytyczną sekcją ...
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 }
źródło
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); } } }
Stosowanie:
DelayedDelegate.Add(MyMethod,5); void MyMethod() { MessageBox.Show("5 Seconds Later!"); }
źródło
Pomyślałem, że idealnym rozwiązaniem byłoby posiadanie timera do obsługi opóźnionego działania. FxCop nie lubi, gdy interwał jest krótszy niż jedna sekunda. Muszę opóźnić moje działania do PO zakończeniu sortowania według kolumny DataGrid. Pomyślałem, że rozwiązaniem będzie jednorazowy licznik czasu (AutoReset = false) i działa idealnie. I FxCop nie pozwoli mi zlikwidować ostrzeżenia!
źródło
Będzie to działać na starszych wersjach .NET
Wady: będzie działać w swoim własnym wątku
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(); }
Stosowanie:
var job = CancelableDelay.StartAfter(1000, () => { WorkAfter1sec(); }); job.Cancel(); //to cancel the delayed job
źródło
Nie ma standardowego sposobu opóźnienia wywołania funkcji innej niż użycie timera i zdarzeń.
To brzmi jak wzorzec przeciwwagi GUI opóźniający wywołanie metody, dzięki czemu możesz mieć pewność, że formularz został ukończony. Nie jest to dobry pomysł.
źródło
Opierając się na odpowiedzi Davida O'Donoghue'a, oto zoptymalizowana wersja Opóźnionego delegata:
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(); } } } }
Klasę można nieco ulepszyć, używając unikalnego klucza dla delegatów. Ponieważ jeśli dodasz tego samego delegata po raz drugi przed uruchomieniem pierwszego, możesz mieć problem ze słownikiem.
źródło
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)); }
źródło