Есть ли хороший простой способ отложить вызов функции, позволяя потоку продолжать выполнение?
например
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 должен вызвать bar, которому нужен экземпляр класса foo, который создается ... отсюда отложенный вызов bar (), чтобы гарантировать, что foo полностью создан. почти попахивает плохим дизайном!
РЕДАКТИРОВАТЬ
Я приму к сведению замечания о плохом дизайне! Я долго думал, что смогу улучшить систему, однако эта неприятная ситуация возникает только тогда, когда генерируется исключение, во всех остальных случаях два синглтона сосуществуют очень хорошо. Думаю, я не собираюсь возиться с неприятными асинхронными шаблонами, скорее я собираюсь провести рефакторинг инициализации одного из классов.
Awake
иStart
фазы. НаAwake
этапе вы настраиваете себя, и к концу этого этапа все объекты инициализируются. Во времяStart
фазы объекты могут начать общаться друг с другом.Ответы:
Благодаря современному C # 5/6 :)
public void foo() { Task.Delay(1000).ContinueWith(t=> bar()); } public void bar() { // do stuff }
источник
Я сам искал что-то подобное - я придумал следующее, хотя он использует таймер, он использует его только один раз для начальной задержки и не требует никаких
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 за идею размещения таймера в обратном вызове)
источник
timer
локальной переменной из лямбды, которая привязывается к объекту делегата,cb
приводит к тому, что он поднимается в анонимное хранилище (деталь реализации закрытия), что приведет к тому, чтоTimer
объект будет доступен с точки зрения GC до тех порTimerCallback
, пока доступен сам делегат . Другими словами, гарантируется , чтоTimer
объект не будет обработан сборщиком мусора до тех пор, пока объект делегата не будет вызван пулом потоков.Помимо согласия с конструктивными наблюдениями предыдущих комментаторов, ни одно из решений не было для меня достаточно чистым. .Net 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 (); } }
Для контекста я использую это, чтобы заблокировать привязку
ICommand
к левой кнопке мыши на элементе пользовательского интерфейса. Пользователи делают двойной щелчок, что вызывает разного рода хаос. (Я знаю, что могу также использоватьClick
/DoubleClick
обработчики, но мне нужно решение, которое работает сICommand
s по всем направлениям).public void Execute(object parameter) { if (!IsDebouncing) { IsDebouncing = true; AsyncUtils.DelayCall (DebouncePeriodMsec, () => { IsDebouncing = false; }); _execute (); } }
источник
Похоже, что контроль создания обоих этих объектов и их взаимозависимости должен контролироваться извне, а не между самими классами.
источник
Это действительно очень плохой дизайн, не говоря уже о том, что синглтон сам по себе плохой дизайн.
Однако, если вам действительно нужно отложить выполнение, вы можете сделать следующее:
BackgroundWorker barInvoker = new BackgroundWorker(); barInvoker.DoWork += delegate { Thread.Sleep(TimeSpan.FromSeconds(1)); bar(); }; barInvoker.RunWorkerAsync();
Однако это вызовет
bar()
отдельный поток. Если вам нужно вызватьbar()
исходный поток, вам может потребоваться переместитьbar()
вызов вRunWorkerCompleted
обработчик или немного взломатьSynchronizationContext
.источник
Что ж, я должен согласиться с "дизайнерской" точкой ... но вы, вероятно, можете использовать Монитор, чтобы сообщить одному, когда другой минует критическую секцию ...
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 }
источник
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!"); }
источник
Я подумал, что идеальным решением было бы иметь таймер, обрабатывающий отложенное действие. FxCop не любит, когда у вас интервал меньше одной секунды. Мне нужно отложить свои действия до тех пор, пока ПОСЛЕ того, как мой DataGrid не завершит сортировку по столбцу. Я решил, что решением будет одноразовый таймер (AutoReset = false), и он отлично работает. И FxCop не позволит мне подавить предупреждение!
источник
Это будет работать либо в более старых версиях .NET
Cons: будет выполняться в собственном потоке
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
источник
Не существует стандартного способа отложить вызов функции, кроме использования таймера и событий.
Это звучит как анти-шаблон графического интерфейса пользователя для задержки вызова метода, чтобы вы могли быть уверены, что форма завершена. Не хорошая идея.
источник
Основываясь на ответе Дэвида О'Донохью, вот оптимизированная версия 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(); } } } }
Класс можно было бы немного улучшить, используя уникальный ключ для делегатов. Потому что, если вы добавите того же делегата во второй раз до того, как сработал первый, у вас могут возникнуть проблемы со словарем.
источник
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)); }
источник