スレッドの実行を継続しながら、関数呼び出しを遅らせる素敵な簡単な方法はありますか?
例えば.
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が作成されるfooクラスのインスタンスを必要とするbarを呼び出す必要があるということです...したがって、fooが完全にインスタンス化されることを保証するbar()への遅延呼び出し。悪いデザインのほとんどスナック!
[〜#〜] edit [〜#〜]
悪いデザインについては、アドバイスを受けながらポイントを取ります!私はシステムを改善できるかもしれないと長い間考えていましたが、この厄介な状況onlyは、例外がスローされたときに発生します。 2つのシングルトンは非常にうまく共存します。私は厄介な非同期パターンに対処するのではなく、クラスの1つの初期化をリファクタリングするつもりだと思います。
最新の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 に感謝)
以前のコメンターの設計の観察に同意することは別として、どのソリューションも私にとって十分にきれいではありませんでした。 .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
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は、1秒未満の間隔があると気に入らない。 DataGridが列による並べ替えを完了するまで、アクションを遅らせる必要があります。ワンショットタイマー(AutoReset = false)が解決策であり、完全に機能すると考えました。そして、FxCopは警告を抑制させません!
これは、.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
タイマーとイベントを使用する以外に、関数の呼び出しを遅らせる標準的な方法はありません。
これは、フォームのレイアウトが完了したことを確認できるように、メソッドの呼び出しを遅らせるGUIアンチパターンのように聞こえます。良い考えではありません。
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();
}
}
}
}
デリゲートに一意のキーを使用することで、クラスをわずかに改善できます。最初のデリゲートが起動する前に同じデリゲートを2回追加すると、辞書に問題が発生する可能性があるためです。
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));
}