如何让UI线程等待信号量,但是处理其他调度程序请求? (喜欢什么MessageBox.Show本机) [英] How can I get the UI thread to wait on a semaphore, but process additional dispatcher requests? (like what MessageBox.Show does natively)

查看:161
本文介绍了如何让UI线程等待信号量,但是处理其他调度程序请求? (喜欢什么MessageBox.Show本机)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

通常,当UI线程调用类似 MessageBox.Show()时,当前的代码执行不会持续到用户单击确定,但程序将继续运行在UI线程上调度的其他代码。



这个问题,我在一次调用的UI线程上调度的太多委托人遇到了一个问题。我想暂停在某些点继续执行。



在我的新的错误处理程序中,我使用信号量来确保一次处理不超过一个错误。我发送一个MessageBox以提醒用户,当他们点击确定时,我发布信号量,允许处理下一个错误。



问题是它是没有预期的行为。如果两次调用HandleError发生在同一时间,则第一个调用MessageBox.Show,并且第二个阻塞UI Thread。奇怪的是,调用调用$ code> MessageBox.Show()永远不会被执行 - 整个应用程序只是挂起 - 所以当用户点击确定时应该释放的信号量是永久锁定这个解决方案缺少什么?

  private static ConcurrentDictionary< Exception,DateTime> QueuedErrors = new ConcurrentDictionary< Exception,DateTime>(); 
private static Semaphore Lock_HandleError = new Semaphore(1,1); //一次只能处理一个错误
private static void ErrorHandled(Exception ex)
{
DateTime value;
QueuedErrors.TryRemove(ex,out value);
Lock_HandleError.Release();
}

private static bool ExceptionHandlingTerminated = false;
public static void HandleError(Exception ex,string extraInfo =,bool showMsgBox = true,bool resetApplication = true)
{
if(ExceptionHandlingTerminated || App.Current == null)return ;
QueuedErrors.TryAdd(ex,DateTime.Now); //线程安全跟踪正在抛出的同时发生的错误

Lock_HandleError.WaitOne(); //这将确保一次只处理一个错误。

if(ExceptionHandlingTerminated || App.Current == null)
{
ErrorHandled(ex);
return;
}

try
{
if(QueuedErrors.Count> 10)
{
ExceptionHandlingTerminated = true;
抛出新的异常(后台太多同时发生的错误);
}

if(Thread.CurrentThread!= Dispatcher.CurrentDispatcher.Thread)
{
//我们不在UI线程上,我们必须调度这个呼叫。
((App)App.Current).Dispatcher.BeginInvoke((Action< Exception,string,bool,bool>)
delegate(异常_ex,string _extraInfo,bool _showMsgBox,bool _resetApplication)
{
ErrorHandled(_ex); //释放产生的信号量HandleError调用
HandleError(_ex,_extraInfo,_showMsgBox,_resetApplication);
},DispatcherPriority.Background,新对象[ ] {ex,extraInfo,showMsgBox,resetApplication});
return;
}

if(showMsgBox)
{
//如果UI正在处理可视树事件(如IsVisibleChanged),则在显示一个MessageBox如下所述:http://social.msdn.microsoft.com/forums/en-US/wpf/thread/44962927-006e-4629-9aa3-100357861442
//解决方案是调度和排队消息框。我们必须使用BeginInvoke,因为在这种情况下调度程序处理被暂停。
Dispatcher.CurrentDispatcher.BeginInvoke((Action< Exception,String>)delegate(Exception _ex,String _ErrMessage)
{
MessageBox.Show(_ErrMessage,MUS Application Error,MessageBoxButton.OK ,MessageBoxImage.Error);
ErrorHandled(_ex); //释放由产生执行的信号量HandleError调用
},DispatcherPriority.Background,new object [] {ex,extraInfo});
}
else
{
ErrorHandled(ex);
}
}
catch(异常terminateError)
{
ExceptionHandlingTerminated = true;
Dispatcher.CurrentDispatcher.BeginInvoke((Action< String>)delegate(String _fatalMessage)
{
MessageBox.Show(_fatalMessage,致命错误,MessageBoxButton.OK,MessageBoxImage.Stop);
if(App.Current!= null)App.Current.Shutdown(1);
},DispatcherPriority.Background,new object [] {fatalMessage});
ErrorHandled(ex); //释放此HandleError调用取得的信号量,这将允许所有其他排队的HandleError调用继续并检查ExceptionHandlingTerminated标志。
}
}

不要担心奇怪的丢失消息字符串,

解决方案

假设您要查找的行为是针对每个消息框等待,直到上一个消息框被清除,你想要一个这样的模式:


  1. 事件源将消息排队阻塞队列

  2. 事件源调用后台线程上的一个委托来处理队列

  3. 进程队列委托采取锁定(如你所做),出现一个消息,并调用(同步)UI线程来显示消息。然后它循环,做同样的事情,直到队列是emtpy。

所以这样的事情(未经测试的代码):

  private static ConcurrentQueue< Tuple< Exception,DateTime>> QueuedErrors = new ConcurrentQueue< Tuple< Exception,DateTime>>(); 
private static Object Lock_HandleError = new Object();
public static void HandleError(Exception ex,string extraInfo =,bool showMsgBox = true,bool resetApplication = true)
{
QueuedErrors.Enqueue(new Tuple< Exception,String> ,DateTime.Now));
ThreadPool.QueueUserWorkItem(()=>((App)App.Current).Dispatcher.Invoke((Action)
()=> {
lock(Lock_HandleError)
元组< Exception,DateTime> currentEx;
while(QueuedErrors.TryDequeue(out currentEx))
MessageBox.Show(
currentEx.Item1,//异常
MUS应用程序错误,
MessageBoxButton.OK,
MessageBoxImage.Error);
}))
);


Normally, when the UI thread calls something like MessageBox.Show(), the current code execution doesn't continue until the user clicks OK, but the program will continue to run other code dispatched on the UI thread.

In this question, I had a problem with too many delegates dispatched on the UI Thread being called at once. I wanted to pause at certain points before continuing execution.

In my new Error handler, I use semaphores to make sure no more than one error is being handled at once. I dispatch a MessageBox to alert the user, and when they click "OK", I release the semaphore, allowing the next error to be processed.

The problem is that it's not behaving as expected. If two dispatched calls to HandleError happen at the same time, the first one Dispatches a call to MessageBox.Show, and the second one blocks the UI Thread. Strangely, the dispatched call to MessageBox.Show() never gets executed - the entire Application just hangs - so the semaphore that's supposed to be released when the user clicked "OK" is permanently locked. What is this solution lacking?

private static ConcurrentDictionary<Exception, DateTime> QueuedErrors = new ConcurrentDictionary<Exception, DateTime>();
private static Semaphore Lock_HandleError = new Semaphore(1, 1); //Only one Error can be processed at a time
private static void ErrorHandled(Exception ex)
{
    DateTime value;
    QueuedErrors.TryRemove(ex, out value);
    Lock_HandleError.Release();
}

private static bool ExceptionHandlingTerminated = false;
public static void HandleError(Exception ex, string extraInfo = "", bool showMsgBox = true, bool resetApplication = true)
{
    if( ExceptionHandlingTerminated || App.Current == null) return;
    QueuedErrors.TryAdd(ex, DateTime.Now); //Thread safe tracking of how many simultaneous errors are being thrown

    Lock_HandleError.WaitOne(); //This will ensure only one error is processed at a time.

    if( ExceptionHandlingTerminated || App.Current == null )
    {
        ErrorHandled(ex);
        return;
    }

    try
    {
        if( QueuedErrors.Count > 10 )
        {
            ExceptionHandlingTerminated = true;
            throw new Exception("Too many simultaneous errors have been thrown in the background.");
        }

        if( Thread.CurrentThread != Dispatcher.CurrentDispatcher.Thread )
        {
            //We're not on the UI thread, we must dispatch this call.
            ((App)App.Current).Dispatcher.BeginInvoke((Action<Exception, string, bool, bool>)
                delegate(Exception _ex, string _extraInfo, bool _showMsgBox, bool _resetApplication)
                {
                    ErrorHandled(_ex); //Release the semaphore taken by the spawning HandleError call
                    HandleError(_ex, _extraInfo, _showMsgBox, _resetApplication);
                }, DispatcherPriority.Background, new object[] { ex, extraInfo, showMsgBox, resetApplication });
            return;
        }

        if( showMsgBox )
        {
            //IF the UI is processing a visual tree event (such as IsVisibleChanged), it throws an exception when showing a MessageBox as described here: http://social.msdn.microsoft.com/forums/en-US/wpf/thread/44962927-006e-4629-9aa3-100357861442
            //The solution is to dispatch and queue the MessageBox. We must use BeginInvoke because dispatcher processing is suspended in such cases.
            Dispatcher.CurrentDispatcher.BeginInvoke((Action<Exception, String>)delegate(Exception _ex, String _ErrMessage)
            {
                MessageBox.Show(_ErrMessage, "MUS Application Error", MessageBoxButton.OK, MessageBoxImage.Error);
                ErrorHandled(_ex); //Release the semaphore taken by the spawning HandleError call
            }, DispatcherPriority.Background, new object[]{ ex, extraInfo });
        }
        else
        {
            ErrorHandled(ex);
        }
    }
    catch( Exception terminatingError )
    {
        ExceptionHandlingTerminated = true;
        Dispatcher.CurrentDispatcher.BeginInvoke((Action<String>)delegate(String _fatalMessage)
        {
            MessageBox.Show(_fatalMessage, "Fatal Error", MessageBoxButton.OK, MessageBoxImage.Stop);
            if( App.Current != null ) App.Current.Shutdown(1);
        }, DispatcherPriority.Background, new object[] { fatalMessage });
        ErrorHandled(ex); //Release the semaphore taken by this HandleError call which will allow all other queued HandleError calls to continue and check the ExceptionHandlingTerminated flag.
    }
}

Don't worry about the odd missing message string, I cut a lot of details out to make the pattern clearer.

解决方案

Assuming that the behavior you are looking for is for each message box to wait in turn until the previous message box has been cleared, you want a pattern like this:

  1. The event source queues the message in a blocking queue
  2. The event source invokes a delegate on a background thread to "Process the Queue"
  3. The "Process the Queue" delegate takes a lock (as you have done), dequeues a message, and Invokes (synchronously) to the UI thread to show the message. Then it loops, doing the same thing until the queue is emtpy.

So something like this (untested code ahead):

private static ConcurrentQueue<Tuple<Exception, DateTime>> QueuedErrors = new ConcurrentQueue<Tuple<Exception, DateTime>>();
private static Object Lock_HandleError = new Object();
public static void HandleError(Exception ex, string extraInfo = "", bool showMsgBox = true, bool resetApplication = true)
{
    QueuedErrors.Enqueue(new Tuple<Exception, String>(ex, DateTime.Now));
    ThreadPool.QueueUserWorkItem(()=>((App)App.Current).Dispatcher.Invoke((Action)
            () => {
                lock (Lock_HandleError)
                    Tuple<Exception, DateTime> currentEx;
                    while (QueuedErrors.TryDequeue(out currentEx))
                        MessageBox.Show(
                           currentEx.Item1, // The exception
                           "MUS Application Error", 
                           MessageBoxButton.OK, 
                           MessageBoxImage.Error);
            }))
    );

这篇关于如何让UI线程等待信号量,但是处理其他调度程序请求? (喜欢什么MessageBox.Show本机)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆