У меня многопоточное приложение с некоторой фоновой обработкой. Он имеет долгосрочные обновления пользовательского интерфейса (в самом потоке пользовательского интерфейса), которые вызываются из фонового потока через известный ресурс в MSDN . Я не могу сократить эти обновления пользовательского интерфейса, так как они, наконец, сделаны во внешней библиотеке (1).
Теперь из этого фонового потока Я хочу асинхронно вызывать (используя BeginInvoke()
) эти обновления в потоке пользовательского интерфейса, но только если предыдущее обновление еще не завершено. Если нет, я бы хотел просто пропустить это обновление. Это предотвратит переполнение очереди сообщений Windows в случае, если вызовы выполняются быстрее, чем способ вызванного метода.
Мое текущее решение таково:
В методе, который выполняется в потоке пользовательского интерфейса, я вхожу и выхожу из экземпляра ReaderWriterLockSlim. В фоновом потоке я пытаюсь ввести экземпляр с нулевым тайм-аутом. В случае успеха я вызываю «BeginInvoke ()» и снова выхожу. Когда не удается, я пропускаю вызов метода вообще.
public void Update(double x, double y)
{
_updateLock.EnterWriteLock();
try
{ //...long running task... }
finally
{ _updateLock.ExitWriteLock(); }
}
//....
void Provider_PositionChanged(object sender, SpecialEventArgs e)
{
if (_updateLock.TryEnterWriteLock(0)) //noone is currently executing an update?
{
try { myUiControl.BeginInvoke(/*...*/); }
finally { _updateLock.ExitWriteLock(); }
}
Это все работает, но есть ли более элегантное решение? Как просто из одного потока проверить, выполняется ли метод в каком-либо (другом) потоке?
Спасибо за любые ответы!
UPDATE:
Ханс Пассант помог мне с ответом. См. Решение ниже. Надеюсь, это поможет кому-то еще.
/// <summary>
/// This class enqueues asynchronously executing actions (that are running on another thread), but allows
/// to execute only one action at a time. When busy, newly enqueued actions are dropped.
/// Any enqueued action is required to call Done() on this when it has finished, to allow further actions
/// to execute afterwards.
/// </summary>
/// <remarks>This class is intended to help prevent stacking UI-Updates when the CPU or other resources
/// on the machine are not able to handle the amount of updates requested. However, the user
/// must keep in mind, that using this class may result
/// in dropped updates and that the last requested update is not always executed.</remarks>
public class ActionBouncer
{
/// <summary>
/// A event that signals the idle/busy state. Idle means, that no action is currently executing.
/// </summary>
private ManualResetEvent _idle = new ManualResetEvent(true);
/// <summary>
/// Enqueues the specified action, executing it when this bouncer
/// is currently idle.
/// </summary>
/// <param name="action">The action.</param>
public void Enqueue(Action action)
{
if (_idle.WaitOne(0)) //are we idle now? (Remark: This check and the reset below is not thread-safe (thanks to s.skov))
{
_idle.Reset(); //go to busy state
action(); //execute the action now.
}//else drop the action immediately.
}
/// <summary>
/// Signal the bouncer, that the currently executing asynchronous action is done, allowing
/// subsequent requests to execute.
/// This must get explicitly called (in code) at the end of the asynchronous action.
/// </summary>
public void Done()
{
_idle.Set();
}
}