我有一个简单的模式只能运行一次代码。它主要用于在UI上更新某些内容,而在后台可能会经常更改。
private bool _updating;
private void UpdateSomething()
{
if (!_updating)
{
_updating = true;
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
_updating = false;
DoSomething();
}), DispatcherPriority.Background);
}
}
我更喜欢将样板代码放在一个简单的方法中:
public static void RunOnce(Action action, ref bool guard)
{
if (!guard)
{
guard = true;
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
guard = false;
action();
}), DispatcherPriority.Background);
}
}
并称之为:
void UpdateSomething()
{
RunOnce(DoSomething, ref _updating);
}
但是,这不起作用,因为您无法在匿名方法中使用ref参数。 有没有解决方法,例如固定ref参数并在执行方法时释放它?
答案 0 :(得分:2)
你可以这样做:
public static void RunOnce(Action action, ref RunOnceToken token)
{
if (token == null || token.IsCompleted)
{
token = new RunOnceToken(
Application.Current.Dispatcher.BeginInvoke(
action,
DispatcherPriority.Background));
}
}
public sealed class RunOnceToken : IDisposable
{
private DispatcherOperation _operation;
public RunOnceToken(DispatcherOperation operation)
{
if (operation != null &&
operation.Status != DispatcherOperationStatus.Completed &&
operation.Status != DispatcherOperationStatus.Aborted)
{
_operation = operation;
_operation.Completed += OnCompletedOrAborted;
_operation.Aborted += OnCompletedOrAborted;
}
}
private void OnCompletedOrAborted(object sender, EventArgs e)
{
this.Dispose();
}
public bool IsCompleted
{
get { return _operation == null; }
}
public void Dispose()
{
var operation = _operation;
if (operation == null)
return;
_operation = null;
operation.Completed -= OnCompletedOrAborted;
operation.Aborted -= OnCompletedOrAborted;
}
}
您的示例用法将更改为:
private RunOnceToken _updateToken;
private void UpdateSomething()
{
RunOnce(DoSomething, ref _updateToken);
}
如果你永远不清除你的令牌副本,那真的不重要,因为包装后的DispatcherOperation
会在完成时被清除,以避免泄漏action
或它捕获的任何值。
如果不明显,这些都不是并发安全的;我假设上面的所有内容都只能从UI线程访问。
一个有用的增强功能可能是为DispatcherPriority
添加一个可选的RunOnce
参数,以便您可以控制用于计划action
的优先级(如果它可能取消已经安排的操作)被安排在较低的优先级。)
答案 1 :(得分:0)
我不知道DispatcherOperation
的存在,但是看到Mike Strobel回答了我写的以下代码。我不是100%知道它,但似乎不需要太多样板。
public static class DispatcherExtensions {
public static int clearInterval = 10_000;
private static long time => DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
private static long lastClearTime = time;
private static Dictionary<int, DispatcherOperation> currOperations = new Dictionary<int, DispatcherOperation>();
private static object sync = new object();
public static void invokeLastAsync(this Dispatcher d, Action a, DispatcherPriority p = DispatcherPriority.Background, [CallerFilePath]object key1 = null, [CallerLineNumber]object key2 = null) {
lock (sync) {
DispatcherOperation dop;
var k = key1.GetHashCode() ^ key2.GetHashCode();
if (currOperations.ContainsKey(k)) {
dop = currOperations[k];
currOperations.Remove(k);
dop.Abort();
}
dop = d.BeginInvoke(a, p);
clearOperations(false);
currOperations.Add(k, dop);
}
}
public static void clearOperations(bool force = true) {
var ct = time;
if (!force && ct - lastClearTime < clearInterval) return;
var nd = new Dictionary<int, DispatcherOperation>();
foreach (var ao in currOperations) {
var s = ao.Value.Status;
if (s == DispatcherOperationStatus.Completed
|| s == DispatcherOperationStatus.Aborted)
nd.Add(ao.Key, ao.Value);
}
currOperations = nd;
lastClearTime = ct;
}
}
基本上,扩展方法将文件路径和行号作为关键字,以将DispacherOperation
实例存储在字典中,如果该键已经有一个操作,则它被中止并被新操作替换。定期从不再调用的已完成/已终止的操作中清除字典。
用法很简单:
private int initCount = 0;
private int invokeCount = 0;
private void updateSomething() {
initCount++;
view.Dispatcher.invokeLastAsync(() => {
Console.WriteLine($@"invoked {++invokeCount}/{initCount}");
});
}
到目前为止,我还没有遇到任何问题。也许其他人可能会看到一些薄弱环节。