是否有一个很好的简单方法来延迟函数调用,同时让线程继续执行?
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
}
我知道这可以通过使用计时器和事件处理程序来实现,但我想知道是否有一种标准的c#方式来实现这一目标?
如果有人好奇,那么需要的原因是foo()和bar()属于不同的(单例)类,我需要在特殊情况下互相调用。问题是这是在初始化时完成的所以foo需要调用bar,它需要一个正在创建的foo类的实例...因此延迟调用bar()以确保foo完全实例化。读回来几乎是糟糕的设计!
修改
我会在建议下接受关于糟糕设计的观点!我一直认为我可能能够改进系统,但是,这种令人讨厌的情况仅在抛出异常时发生,在所有其他时候两个单身人士共存非常好。我认为我不会用令人讨厌的异步模式来解决问题,而是我要重构其中一个类的初始化。
答案 0 :(得分:120)
感谢现代C#5/6:)
public void foo()
{
Task.Delay(1000).ContinueWith(t=> bar());
}
public void bar()
{
// do stuff
}
答案 1 :(得分:92)
我一直在寻找类似的东西 - 我想出了以下内容,虽然它确实使用了计时器,但它只使用了一次初始延迟,并且不需要任何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关于在回调中处理计时器的想法)
答案 2 :(得分:15)
除了同意之前评论者的设计观察结果外,没有一个解决方案对我来说足够干净。 .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 ();
}
}
对于上下文,我使用它来去除绑定到UI元素上的鼠标左键的ICommand
。用户双击导致各种破坏。 (我知道我也可以使用Click
/ DoubleClick
处理程序,但我想要一个可以全面使用ICommand
的解决方案。
public void Execute(object parameter)
{
if (!IsDebouncing) {
IsDebouncing = true;
AsyncUtils.DelayCall (DebouncePeriodMsec, () => {
IsDebouncing = false;
});
_execute ();
}
}
答案 3 :(得分:7)
这听起来像控制这些对象的创建及其相互依赖需要在外部控制,而不是在类本身之间。
答案 4 :(得分:5)
这确实是一个非常糟糕的设计,更不用说单身人士本身就是糟糕的设计。
但是,如果您确实需要延迟执行,那么您可以采取以下措施:
BackgroundWorker barInvoker = new BackgroundWorker();
barInvoker.DoWork += delegate
{
Thread.Sleep(TimeSpan.FromSeconds(1));
bar();
};
barInvoker.RunWorkerAsync();
但是,这将在单独的线程上调用bar()
。如果您需要在原始主题中调用bar()
,则可能需要将bar()
调用移至RunWorkerCompleted
处理程序,或对SynchronizationContext
进行一些黑客攻击。
答案 5 :(得分:2)
好吧,我必须同意“设计”一点......但是你可以使用一个监视器让别人知道另一个人何时超过了关键部分......
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
}
答案 6 :(得分:2)
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!");
}
答案 7 :(得分:1)
我虽然完美的解决方案是让计时器处理延迟动作。 当你的间隔小于一秒时,FxCop不喜欢。 我需要延迟我的操作,直到我的DataGrid完成按列排序。 我认为一次性计时器(AutoReset = false)将是解决方案,并且它完美地运行。 并且,FxCop不会让我压制警告!
答案 8 :(得分:1)
这适用于旧版本的.NET
缺点:将在自己的线程中执行
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
答案 9 :(得分:0)
除了使用计时器和事件之外,没有标准的方法来延迟对函数的调用。
这听起来像延迟调用方法的GUI反模式,以便您可以确定表单已完成布局。不是个好主意。
答案 10 :(得分:0)
基于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();
}
}
}
}
通过为代理使用唯一键,可以稍微改进一下这个类。 因为如果您在第一次委托之前第二次添加相同的委托,则可能会出现字典问题。
答案 11 :(得分:0)
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));
}