我怎样才能在UI线程等待一个信号量,但处理额外的调度要求? (像什么的MessageBox.show做本地)信号量、线程、UI、MessageBox

2023-09-07 13:27:11 作者:YXY你是我的宝

通常情况下,当UI线程调用像的MessageBox.show(),目前的code执行不会继续下去,直到用户点击确定,但方案将继续运行其他code派出UI线程上。

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.

在this问题,我有太多的代表派遣的UI线程被调用时一旦出了问题。我想在某些地方继续执行之前暂停。

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.

现在的问题是,它不是按预期。如果两个分派呼叫的HandleError发生的同时,第一个调度呼叫的MessageBox.show,而第二个块中的UI线程。奇怪的是,到的MessageBox.show派出调用()从来没有被执行 - 整个应用程序只是挂起 - 这样是应该的信号量,当用户点击确定被释放的永久锁定。这是什么产品所没有的?

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.

推荐答案

假设你正在寻找的行为是为每一个消息框,等待依次直到previous消息框已被清除,你想一个模式像这样的:

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:

在该事件源队列阻塞队列的消息 在该事件源调用在后台线程来过程中的队列委托 在进程队列授人需要一个锁(因为你这样做),取出一个消息,并调用(同步)到UI线程来显示信息。然后循环,做同样的事,直到队列为EMPTY的。

因此​​,像这样(未经测试code未来):

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);
            }))
    );