可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Is there a nice simple method of delaying a function call whilst letting the thread continue executing?
e.g.
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
}
I'm aware that this can be achieved by using a timer and event handlers, but I was wondering if there is a standard c# way to achieve this?
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 !
EDIT
I'll take the points about bad design under advisement! I've long thought that I might be able to improve the system, however, this nasty situation only occurs when an exception is thrown, at all other times the two singletons co-exist very nicely. I think that I'm not going to messaround with nasty async-patters, rather I'm going to refactor the initialisation of one of the classes.
回答1:
Thanks to modern C# 5/6 :)
public void foo()
{
Task.Delay(1000).ContinueWith(t=> bar());
}
public void bar()
{
// do stuff
}
回答2:
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)
回答3:
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 ICommand
s across the board).
public void Execute(object parameter)
{
if (!IsDebouncing) {
IsDebouncing = true;
AsyncUtils.DelayCall (DebouncePeriodMsec, () => {
IsDebouncing = false;
});
_execute ();
}
}
回答4:
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.
回答5:
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
.
回答6:
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
}
回答7:
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!");
}
回答8:
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!
回答9:
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.
回答10:
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.
回答11:
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));
}
回答12:
This will work either on older versions of .NET
Cons: will execute in its own thread
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();
}
Usage:
var job = CancelableDelay.StartAfter(1000, () => { WorkAfter1sec(); });
job.Cancel(); //to cancel the delayed job