首先,我们将处理您所描述的情况,在这种情况下,该方法始终从 UI 线程或其他一些同步上下文中使用。这Run
方法本身可以是async
通过同步上下文为我们处理所有的编组。
如果我们正在运行,我们只需设置下一个存储的操作。如果不是,那么我们表明我们现在正在运行,等待该操作,然后继续等待下一个操作,直到没有下一个操作。我们确保每当完成时我们都会表明我们已完成运行:
public class EventThrottler
{
private Func<Task> next = null;
private bool isRunning = false;
public async void Run(Func<Task> action)
{
if (isRunning)
next = action;
else
{
isRunning = true;
try
{
await action();
while (next != null)
{
var nextCopy = next;
next = null;
await nextCopy();
}
}
finally
{
isRunning = false;
}
}
}
private static Lazy<EventThrottler> defaultInstance =
new Lazy<EventThrottler>(() => new EventThrottler());
public static EventThrottler Default
{
get { return defaultInstance.Value; }
}
}
因为该类至少在一般情况下是专门从 UI 线程中使用的,所以通常只需要一个,所以我添加了一个默认实例的便利属性,但因为它可能仍然有意义,因为有更多比程序中的一个,我没有将其设置为单例。
Run
接受一个Func<Task>
其想法是它通常是一个异步 lambda。它可能看起来像:
public class Foo
{
public void SomeEventHandler(object sender, EventArgs args)
{
EventThrottler.Default.Run(async () =>
{
await Task.Delay(1000);
//do other stuff
});
}
}
好吧,简单来说,这是一个处理从不同线程调用事件处理程序的情况的版本。我知道您说过您假设它们都是从 UI 线程调用的,但我对此进行了一些概括。这意味着锁定对某个类型的实例字段的所有访问lock
块,但并未实际执行 a 内的函数lock
堵塞。最后一部分不仅对于性能很重要,还可以确保我们不会阻止项目仅设置next
字段,而且还要避免该操作也调用 run 时出现问题,这样它就不需要处理重入问题或潜在的死锁。这种在锁块中执行操作,然后根据锁中确定的条件进行响应的模式意味着设置局部变量来指示锁结束后应该执行的操作。
public class EventThrottlerMultiThreaded
{
private object key = new object();
private Func<Task> next = null;
private bool isRunning = false;
public void Run(Func<Task> action)
{
bool shouldStartRunning = false;
lock (key)
{
if (isRunning)
next = action;
else
{
isRunning = true;
shouldStartRunning = true;
}
}
Action<Task> continuation = null;
continuation = task =>
{
Func<Task> nextCopy = null;
lock (key)
{
if (next != null)
{
nextCopy = next;
next = null;
}
else
{
isRunning = false;
}
}
if (nextCopy != null)
nextCopy().ContinueWith(continuation);
};
if (shouldStartRunning)
action().ContinueWith(continuation);
}
}