C# 延迟函数调用
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/545533/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Delayed function calls
提问by TK.
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?
我知道这可以通过使用计时器和事件处理程序来实现,但我想知道是否有标准的 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 !
如果有人好奇,需要这样做的原因是 foo() 和 bar() 在不同的(单例)类中,我需要在特殊情况下相互调用。问题是这是在初始化时完成的,所以 foo 需要调用 bar ,这需要正在创建的 foo 类的实例......因此延迟调用 bar() 以确保 foo 完全实例化.. 读回几乎是糟糕的设计!
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 onlyoccurs 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.
我会根据建议采取有关不良设计的观点!我一直认为我可以改进系统,但是,这种令人讨厌的情况只会在抛出异常时发生,而在其他时候,这两个单例可以很好地共存。我认为我不会与讨厌的异步模式混为一谈,而是要重构其中一个类的初始化。
采纳答案by Korayem
Thanks to modern C# 5/6 :)
感谢现代 C# 5/6 :)
public void foo()
{
Task.Delay(1000).ContinueWith(t=> bar());
}
public void bar()
{
// do stuff
}
回答by Adam Ralph
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.
这听起来像是对这些对象的创建及其相互依赖的控制需要在外部进行控制,而不是在类本身之间进行控制。
回答by Marc Gravell
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
}
回答by ng5000
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.
这听起来像是延迟调用方法的 GUI 反模式,以便您可以确定表单已完成布局。不是个好主意。
回答by Anton Gogolev
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
.
但是,这将bar()
在单独的线程上调用。如果您需要调用bar()
原始线程,您可能需要将bar()
调用移动到RunWorkerCompleted
处理程序或使用SynchronizationContext
.
回答by dodgy_coder
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 ...
我自己一直在寻找类似的东西 - 我想出了以下内容,尽管它确实使用了计时器,但它仅在初始延迟中使用一次,并且不需要任何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
}
(thanks to Fred Deschenesfor the idea of disposing the timer within the callback)
(感谢Fred Deschenes在回调中处理计时器的想法)
回答by David O'Donoghue
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!");
}
回答by Jim Mahaffey
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!
我认为完美的解决方案是让计时器处理延迟动作。FxCop 不喜欢间隔少于一秒的情况。我需要延迟我的操作,直到我的 DataGrid 完成按列排序之后。我认为一次性计时器(AutoReset = false)将是解决方案,并且它运行良好。而且,FxCop 不会让我压制警告!
回答by Pic Mickael
Building upon the answer from David O'Donoghue here is an optimized version of the Delayed Delegate:
基于 David O'Donoghue 的回答,这里是延迟委托的优化版本:
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.
通过为代表使用唯一键,可以稍微改进该类。因为如果您在第一个委托之前第二次添加相同的委托,您可能会遇到字典问题。
回答by Koray
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));
}
回答by cod3monk3y
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 threadpretty simple:
除了同意之前评论者的设计观察之外,没有一个解决方案对我来说足够干净。.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 ();
}
}
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).
对于上下文,我使用它来消除ICommand
绑定到 UI 元素上的鼠标左键。用户正在双击,这造成了各种破坏。(我知道我也可以使用Click
/DoubleClick
处理程序,但我想要一个可以全面使用ICommand
s的解决方案)。
public void Execute(object parameter)
{
if (!IsDebouncing) {
IsDebouncing = true;
AsyncUtils.DelayCall (DebouncePeriodMsec, () => {
IsDebouncing = false;
});
_execute ();
}
}