延迟的函数调用

有没有一个很好的简单的方法来延迟函数调用,同时让线程继续执行?

例如:。

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 # 方法来实现这一点?

If anyone is curious, the reason that this is required is that foo() and bar() are in different (singleton) classes which my need to call each other in exceptional circumstances. The problem being that this is done at initialisation so foo needs to call bar which needs an instance of the foo class which is being created... hence the delayed call to bar() to ensure that foo is fully instanciated.. Reading this back almost smacks of bad design !

剪辑

我将接受关于糟糕设计的建议!我一直认为我可以改进这个系统,但是,当抛出一个异常时,这种糟糕的情况就会发生 只有,在其他任何时候,这两个单例模式都能很好地共存。我认为我不打算使用讨厌的异步模式,相反,我将重构其中一个类的初始化。

159363 次浏览

It sounds like the control of the creation of both these objects and their interdependence needs to controlled externally, rather than between the classes themselves.

Well, I'd have to agree with the "design" point... but you can probably use a Monitor to let one know when the other is past the critical section...

    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
}

There is no standard way to delay a call to a function other than to use a timer and events.

This sounds like the GUI anti pattern of delaying a call to a method so that you can be sure the form has finished laying out. Not a good idea.

It's indeed a very bad design, let alone singleton by itself is bad design.

However, if you really do need to delay execution, here's what you may do:

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

This will, however, invoke bar() on a separate thread. If you need to call bar() in the original thread you might need to move bar() invocation to RunWorkerCompleted handler or do a bit of hacking with SynchronizationContext.

I've been looking for something like this myself - I came up with the following, although it does use a timer, it uses it only once for the initial delay, and doesn't require any Sleep calls ...

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
}

(thanks to Fred Deschenes for the idea of disposing the timer within the callback)

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


}




}


}

Usage:

DelayedDelegate.Add(MyMethod,5);


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

I though the perfect solution would be to have a timer handle the delayed action. FxCop doesn't like when you have an interval less then one second. I need to delay my actions until AFTER my DataGrid has completed sorting by column. I figured a one-shot timer (AutoReset = false) would be the solution, and it works perfectly. AND, FxCop will not let me suppress the warning!

Building upon the answer from David O'Donoghue here is an optimized version of the 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();
}
}
}
}

The class could be slightly more improved by using a unique key for the delegates. Because if you add the same delegate a second time before the first one fired, you might get a problem with the dictionary.

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

Aside from agreeing with the design observations of the previous commenters, none of the solutions were clean enough for me. .Net 4 provides Dispatcher and Task classes which make delaying execution on the current thread pretty simple:

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

For context, I'm using this to debounce an ICommand tied to a left mouse button up on a UI element. Users are double clicking which was causing all kinds of havoc. (I know I could also use Click/DoubleClick handlers, but I wanted a solution that works with ICommands across the board).

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


_execute ();
}
}

Thanks to modern C# 5/6 :)

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


public void bar()
{
// do stuff
}

This will work either on older versions of .NET
Cons: will execute in its own thread

class CancellableDelay
{
Thread delayTh;
Action action;
int ms;


public static CancellableDelay StartAfter(int milliseconds, Action action)
{
CancellableDelay result = new CancellableDelay() { ms = milliseconds };
result.action = action;
result.delayTh = new Thread(result.Delay);
result.delayTh.Start();
return result;
}


private CancellableDelay() { }


void Delay()
{
try
{
Thread.Sleep(ms);
action.Invoke();
}
catch (ThreadAbortException)
{ }
}


public void Cancel() => delayTh.Abort();


}

Usage:

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